├── .github ├── ISSUE_TEMPLATE │ ├── backlog-request.md │ ├── bug_report.md │ ├── discussion.md │ └── feature-request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .metadata ├── .vscode ├── keybindings.json └── launch.json ├── README.md ├── README_ko.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── dev │ │ ├── google-services.json │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-hdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-mdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-xhdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-xxhdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-xxxhdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── techtalk │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-en │ │ │ └── strings.xml │ │ │ ├── values-ko │ │ │ └── strings.xml │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── locales_config.xml │ │ │ └── provider_paths.xml │ │ ├── prod │ │ ├── google-services.json │ │ └── res │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── character │ ├── blue_01.svg │ ├── blue_02.svg │ ├── blue_03.svg │ ├── blue_04.svg │ ├── blue_05.svg │ ├── blue_06.svg │ ├── blue_07.svg │ ├── green_01.svg │ ├── green_02.svg │ ├── green_03.svg │ ├── green_04.svg │ ├── green_05.svg │ ├── green_06.svg │ ├── green_07.svg │ ├── purple_01.svg │ ├── purple_02.svg │ ├── purple_03.svg │ ├── purple_04.svg │ ├── purple_05.svg │ ├── purple_06.svg │ ├── purple_07.svg │ ├── red_01.svg │ ├── red_02.svg │ ├── red_03.svg │ ├── red_04.svg │ ├── red_05.svg │ ├── red_06.svg │ └── red_07.svg ├── fonts │ └── pretendard │ │ ├── Pretendard-Bold.otf │ │ ├── Pretendard-Medium.otf │ │ ├── Pretendard-Regular.otf │ │ └── Pretendard-SemiBold.otf ├── icons │ ├── advanced_illust.svg │ ├── ai_interview_logo.svg │ ├── alarm.svg │ ├── ar_up_down.svg │ ├── arrow_down.svg │ ├── arrow_down_thick.svg │ ├── arrow_left.svg │ ├── arrow_left_thick.svg │ ├── arrow_right.svg │ ├── arrow_right_thick.svg │ ├── arrow_up.svg │ ├── arrow_up_thick.svg │ ├── avatar_1.svg │ ├── beginner_illust.svg │ ├── blog.svg │ ├── boomark.svg │ ├── bullet.svg │ ├── camera.svg │ ├── chat_bubble_tale.svg │ ├── check.svg │ ├── check_note.svg │ ├── check_thick.svg │ ├── circle_small_close.svg │ ├── close.svg │ ├── close_thick.svg │ ├── common_interview_logo.svg │ ├── common_practical_type_illust.svg │ ├── common_single_type_illust.svg │ ├── core_circle.svg │ ├── correct.svg │ ├── correct_indicator.svg │ ├── dark_check_box.svg │ ├── data_structure.svg │ ├── delete_or_wrong.svg │ ├── dice.svg │ ├── error_indicator.svg │ ├── expansion_arrow_indicator.svg │ ├── fail_result.svg │ ├── follow_up_question.svg │ ├── google_logo.svg │ ├── home.svg │ ├── icon_app_bar_left.svg │ ├── icon_mic.svg │ ├── incorrect.svg │ ├── intermediate_illust.svg │ ├── listed_note.svg │ ├── menu_thick.svg │ ├── mistake_note_illust.svg │ ├── more_circle.svg │ ├── new_camera.svg │ ├── new_right_arrow.svg │ ├── note.svg │ ├── pass_result.svg │ ├── pencil.svg │ ├── play.svg │ ├── plus.svg │ ├── plus_thick.svg │ ├── pondering_illusration.svg │ ├── red_alert.svg │ ├── red_warnning_big.svg │ ├── reset.svg │ ├── resume_interview_logo.svg │ ├── right_aligned_right_arrow.svg │ ├── round_blue_circle.svg │ ├── rounded_blue_exclamation.svg │ ├── rounded_camera.svg │ ├── rounded_check.svg │ ├── rounded_check_small_blue.svg │ ├── rounded_check_thick.svg │ ├── rounded_close.svg │ ├── rounded_close_blue.svg │ ├── rounded_close_small_red.svg │ ├── rounded_close_thick.svg │ ├── rounded_exclamation.svg │ ├── rounded_more.svg │ ├── rounded_os.svg │ ├── rounded_plus.svg │ ├── rounded_plus_big.svg │ ├── rounded_send.svg │ ├── rounded_send_inactive.svg │ ├── rounded_top.svg │ ├── rounded_warnning_small_red.svg │ ├── search.svg │ ├── search_thick.svg │ ├── send.svg │ ├── send_activate.svg │ ├── send_up.svg │ ├── sparkle.svg │ ├── star_deco.svg │ ├── study.svg │ ├── summary_note.svg │ ├── talker.svg │ ├── tech_talk_logo.svg │ ├── text_field_mic.svg │ ├── typing_mode_aa.svg │ ├── typing_mode_tooltip.svg │ ├── user.svg │ ├── video_study.svg │ ├── video_upload.svg │ ├── warning.svg │ ├── wemo_check.svg │ ├── wrong_indicator.svg │ ├── youtube_interview_logo.svg │ ├── youtube_logo.svg │ └── youtube_promotion_illust.svg ├── images │ ├── app_icon.png │ ├── avatar_1.png │ ├── blank_profile.png │ ├── induction_practical.png │ ├── induction_resume.png │ ├── induction_single.png │ ├── sparkle.svg │ ├── splash_image.png │ ├── topic_android.png │ ├── topic_data_structure.png │ ├── topic_database.png │ ├── topic_flutter.png │ ├── topic_ios.png │ ├── topic_java.png │ ├── topic_javascript.png │ ├── topic_nest_js.png │ ├── topic_network.png │ ├── topic_operating_system.png │ ├── topic_react.png │ ├── topic_spring.png │ ├── topic_swift.png │ ├── topic_webFrontend.png │ └── welcome_techtalk.svg ├── json │ └── skills.json ├── lottie │ ├── document_loading.json │ ├── done.json │ └── video_uploading.json └── translations │ ├── en.json │ └── ko.json ├── devtools_options.yaml ├── flutter_native_splash-dev.yaml ├── flutter_native_splash-prod.yaml ├── icons_launcher.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Localization │ ├── en.lproj │ │ └── InfoPlist.strings │ └── ko.lproj │ │ └── InfoPlist.strings ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── Runner.xcscheme │ │ ├── dev.xcscheme │ │ └── prod.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── 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-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── LaunchBackground.imageset │ │ │ ├── Contents.json │ │ │ ├── background.png │ │ │ └── darkbackground.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ ├── Runner.entitlements │ ├── en.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ └── ko.lproj │ │ └── Main.strings ├── config │ ├── dev │ │ └── GoogleService-Info.plist │ └── prod │ │ └── GoogleService-Info.plist ├── firebase_app_id_file.json └── firebase_app_id_file_dev.json ├── lib ├── app │ ├── di │ │ ├── app_binding.dart │ │ ├── feature_di_interface.dart │ │ ├── index.dart │ │ └── modules │ │ │ ├── auth_di.dart │ │ │ ├── blog_di.dart │ │ │ ├── chat_di.dart │ │ │ ├── interview_di.dart │ │ │ ├── system_di.dart │ │ │ ├── tech_set_di.dart │ │ │ ├── topic_di.dart │ │ │ ├── user_di.dart │ │ │ └── youtube_di.dart │ ├── entrypoints │ │ ├── main_dev.dart │ │ └── main_prod.dart │ ├── environment │ │ ├── app_version.dart │ │ ├── environment.enum.dart │ │ ├── firebase │ │ │ ├── firebase_options.dart │ │ │ └── firebase_options_dev.dart │ │ └── flavor.dart │ ├── localization │ │ ├── app_locale.dart │ │ ├── locale_keys.g.dart │ │ ├── localization_enum.dart │ │ └── localization_utils.dart │ ├── network │ │ ├── app_dio.dart │ │ └── app_youtube_explode.dart │ ├── notification │ │ ├── app_local_notification.dart │ │ └── app_notification.dart │ ├── router │ │ ├── deeplink │ │ │ ├── deep_link_define.enum.dart │ │ │ ├── deeplink_handler.dart │ │ │ └── deeplink_handler_intent.p.dart │ │ ├── navigation_context.dart │ │ ├── route_extension.dart │ │ ├── router.dart │ │ └── router.g.dart │ ├── style │ │ ├── app_color.dart │ │ ├── app_text_style.dart │ │ ├── app_theme.dart │ │ ├── index.dart │ │ └── themes │ │ │ ├── app_bar_theme.dart │ │ │ ├── filled_button_theme.dart │ │ │ ├── input_decoration_theme.dart │ │ │ └── outlined_button_theme.dart │ └── util │ │ ├── app_format_handler.dart │ │ ├── app_formatter.dart │ │ └── app_logger.dart ├── core │ ├── constants │ │ ├── assets.dart │ │ ├── content_filter_category_type.enum.dart │ │ ├── interview_greetings.dart │ │ ├── job_group.enum.dart │ │ ├── profile_setting_type.enum.dart │ │ ├── slack_notification_type.enum.dart │ │ └── stored_topic.dart │ ├── firebase_pagination_result.dart │ ├── firebase_query_constraints.dart │ ├── helper │ │ ├── bool_extension.dart │ │ ├── cached_image_size_extension.dart │ │ ├── date_time_extension.dart │ │ ├── debouncer.dart │ │ ├── duration_extension.dart │ │ ├── global_event_key.dart │ │ ├── hook_helper.dart │ │ ├── int_extension.dart │ │ ├── list_extension.dart │ │ ├── riverpod_extension.dart │ │ ├── string_extension.dart │ │ ├── string_generator.dart │ │ └── validation_extension.dart │ ├── index.dart │ ├── modules │ │ ├── base_use_case │ │ │ ├── base_no_future_use_case.dart │ │ │ ├── base_no_networking_use_case.dart │ │ │ ├── base_no_param_stream_use_case.dart │ │ │ ├── base_no_param_use_case.dart │ │ │ ├── base_stream_use_case.dart │ │ │ └── base_use_case.dart │ │ ├── converter │ │ │ ├── duration_converter.dart │ │ │ ├── string_to_duration_conveter.dart │ │ │ └── time_stamp_converter.dart │ │ ├── device │ │ │ └── app_device.dart │ │ ├── error_handling │ │ │ └── result.dart │ │ ├── exceptions │ │ │ ├── custom_exception.dart │ │ │ └── network_exception.dart │ │ ├── local │ │ │ └── app_local.dart │ │ └── regex │ │ │ └── app_validator.dart │ ├── query_constraints_applier.dart │ └── services │ │ ├── app_size.dart │ │ ├── dialog_service.dart │ │ ├── slack_notification_service.dart │ │ └── snack_bar_service.dart ├── features │ ├── auth │ │ ├── auth.dart │ │ ├── data_source │ │ │ └── remote │ │ │ │ ├── auth_remote_data_source.dart │ │ │ │ └── auth_remote_data_source_impl.dart │ │ ├── repositories │ │ │ ├── auth_repository.dart │ │ │ ├── auth_repository_impl.dart │ │ │ └── entities │ │ │ │ └── user_account_provider.enum.dart │ │ └── usecases │ │ │ ├── sign_in_oauth_use_case.dart │ │ │ └── sign_out_use_case.dart │ ├── blog │ │ ├── data_sources │ │ │ └── remote │ │ │ │ ├── blog_ref.dart │ │ │ │ ├── blog_remote_data_source.dart │ │ │ │ ├── blog_remote_data_source_impl.dart │ │ │ │ └── models │ │ │ │ ├── blog_main_model.dart │ │ │ │ ├── blog_main_model.g.dart │ │ │ │ ├── company_model.dart │ │ │ │ └── company_model.g.dart │ │ ├── index.dart │ │ ├── repository │ │ │ ├── blog_repository.dart │ │ │ ├── blog_repository_impl.dart │ │ │ ├── entity │ │ │ │ ├── blog_base_entity.dart │ │ │ │ ├── blog_shell_entity.dart │ │ │ │ └── company_set.dart │ │ │ └── enum │ │ │ │ └── blog_platform_type.enum.dart │ │ └── use_case │ │ │ ├── get_blog_contents_use_case.dart │ │ │ └── param │ │ │ └── get_blog_contents_params.dart │ ├── chat │ │ ├── chat.dart │ │ ├── data_source │ │ │ └── remote │ │ │ │ ├── chat_ref.dart │ │ │ │ ├── chat_remote_data_source.dart │ │ │ │ ├── chat_remote_data_source_impl.dart │ │ │ │ └── models │ │ │ │ ├── chat_model.dart │ │ │ │ ├── chat_model.g.dart │ │ │ │ ├── chat_qna_model.dart │ │ │ │ ├── chat_qna_model.g.dart │ │ │ │ ├── chat_room_model.dart │ │ │ │ ├── chat_room_model.g.dart │ │ │ │ ├── follow_up_qna_model.dart │ │ │ │ ├── follow_up_qna_model.g.dart │ │ │ │ ├── resume_field_model.dart │ │ │ │ └── resume_field_model.g.dart │ │ ├── repositories │ │ │ ├── chat_repository.dart │ │ │ ├── chat_repository_impl.dart │ │ │ ├── entities │ │ │ │ ├── answer_chat_entity.dart │ │ │ │ ├── base_qna_entity.dart │ │ │ │ ├── chat_history_collection_entity.dart │ │ │ │ ├── chat_message_entity.dart │ │ │ │ ├── chat_progress_info_entity.dart │ │ │ │ ├── chat_qna_entity.dart │ │ │ │ ├── chat_room_entity.dart │ │ │ │ ├── feedback_chat_entity.dart │ │ │ │ ├── feedback_response_entity.dart │ │ │ │ ├── follow_up_qna_entity.dart │ │ │ │ ├── guide_chat_entity.dart │ │ │ │ ├── proficiency_qna_entity.dart │ │ │ │ ├── question_chat_entity.dart │ │ │ │ ├── resume_qna_entity.dart │ │ │ │ ├── selectable_qna_entity.dart │ │ │ │ ├── youtube_interview_room_entity.dart │ │ │ │ └── youtube_qna_entity.dart │ │ │ └── enums │ │ │ │ ├── ai_answer_progress.enum.dart │ │ │ │ ├── answer_state.enum.dart │ │ │ │ ├── chat_room_progress.enum.dart │ │ │ │ ├── chat_type.enum.dart │ │ │ │ ├── follow_up_status.enum.dart │ │ │ │ ├── interview_level.enum.dart │ │ │ │ ├── interview_progress.enum.dart │ │ │ │ ├── interview_result.dart │ │ │ │ ├── interview_type.enum.dart │ │ │ │ ├── interviewer_type.enum.dart │ │ │ │ ├── qna_type.enum.dart │ │ │ │ └── resume_question_type.enum.dart │ │ └── use_cases │ │ │ ├── create_chat_messages_use_case.dart │ │ │ ├── create_chat_room_use_case.dart │ │ │ ├── create_resume_question_use_case.dart │ │ │ ├── get_chat_message_history_use_case.dart │ │ │ ├── get_chat_qnas_use_case.dart │ │ │ ├── get_chat_rooms_use_case.dart │ │ │ ├── get_one_line_interview_feedback_use_case.dart │ │ │ ├── get_random_qnas_use_case.dart │ │ │ ├── recrod_to_text_use_case.dart │ │ │ ├── report_chat_use_case.dart │ │ │ ├── set_ai_feedback_use_case.dart │ │ │ ├── set_ai_follow_up_question_use_case.dart │ │ │ └── set_gemini_ai_feedback_use_case.dart │ ├── interview │ │ ├── data_source │ │ │ └── local │ │ │ │ ├── boxes │ │ │ │ ├── proficiency_question_history_box.dart │ │ │ │ └── proficiency_question_history_box.g.dart │ │ │ │ ├── interview_local_data_source.dart │ │ │ │ └── interview_local_data_source_impl.dart │ │ ├── index.dart │ │ ├── repository │ │ │ ├── interview_repository.dart │ │ │ └── interview_repository_impl.dart │ │ └── use_case │ │ │ ├── create_proficiency_interview_qna_use_case.dart │ │ │ ├── exception │ │ │ └── ai_creation_failed_exception.dart │ │ │ ├── param │ │ │ └── start_interview_flow_use_case_param.dart │ │ │ └── start_interview_flow_use_case.dart │ ├── system │ │ ├── data_source │ │ │ ├── local │ │ │ │ ├── boxes │ │ │ │ │ ├── system_box.dart │ │ │ │ │ ├── system_box.g.dart │ │ │ │ │ └── system_local_data_source_impl.dart │ │ │ │ └── system_local_data_source.dart │ │ │ └── remote │ │ │ │ ├── models │ │ │ │ ├── version_model.dart │ │ │ │ └── version_model.g.dart │ │ │ │ ├── system_ref.dart │ │ │ │ ├── system_remote_data_source.dart │ │ │ │ └── system_remote_data_source_impl.dart │ │ ├── repositories │ │ │ ├── entities │ │ │ │ ├── version_entity.dart │ │ │ │ └── youtube_gpt_model_type.enum.dart │ │ │ ├── system_repository.dart │ │ │ └── system_repository_impl.dart │ │ ├── system.dart │ │ └── use_cases │ │ │ ├── get_version_info_use_case.dart │ │ │ └── set_entry_flow_use_case.dart │ ├── tech_set │ │ ├── data_source │ │ │ ├── local │ │ │ │ ├── boxes │ │ │ │ │ ├── tech_set_box.dart │ │ │ │ │ └── tech_set_box.g.dart │ │ │ │ ├── tech_set_data_source_impl.dart │ │ │ │ └── tech_set_local_data_source.dart │ │ │ └── remote │ │ │ │ ├── job_group_ref.dart │ │ │ │ ├── model │ │ │ │ ├── job_group_model.dart │ │ │ │ ├── job_group_model.g.dart │ │ │ │ ├── skill_model.dart │ │ │ │ ├── skill_model.g.dart │ │ │ │ ├── tech_set_keys_model.dart │ │ │ │ └── tech_set_keys_model.g.dart │ │ │ │ ├── skill_ref.dart │ │ │ │ ├── tech_set_remote_data_source.dart │ │ │ │ └── tech_set_remote_data_source_impl.dart │ │ ├── repositories │ │ │ ├── entities │ │ │ │ └── tech_set_entity.dart │ │ │ ├── enums │ │ │ │ ├── skill_category.enum.dart │ │ │ │ └── tech_set_type.enum.dart │ │ │ ├── tech_set_repository.dart │ │ │ └── tech_set_repository_impl.dart │ │ ├── tech_set.dart │ │ └── usecases │ │ │ ├── get_jobs_use_case.dart │ │ │ └── get_searched_skills_use_case.dart │ ├── topic │ │ ├── data_source │ │ │ ├── local │ │ │ │ ├── boxes │ │ │ │ │ ├── qna_box.dart │ │ │ │ │ ├── qna_box.g.dart │ │ │ │ │ ├── qna_list_box.dart │ │ │ │ │ └── qna_list_box.g.dart │ │ │ │ ├── topic_local_data_source.dart │ │ │ │ └── topic_local_data_source_impl.dart │ │ │ └── remote │ │ │ │ ├── models │ │ │ │ ├── topic_category_model.dart │ │ │ │ ├── topic_category_model.g.dart │ │ │ │ ├── topic_model.dart │ │ │ │ ├── topic_model.g.dart │ │ │ │ ├── topic_qna_model.dart │ │ │ │ ├── topic_qna_model.g.dart │ │ │ │ ├── wrong_answer_model.dart │ │ │ │ └── wrong_answer_model.g.dart │ │ │ │ ├── topic_remote_data_source.dart │ │ │ │ ├── topic_remote_data_source_impl.dart │ │ │ │ └── topics_ref.dart │ │ ├── repositories │ │ │ ├── entities │ │ │ │ ├── common_qna_entity.dart │ │ │ │ ├── topic_category_entity.dart │ │ │ │ ├── topic_entity.dart │ │ │ │ └── wrong_answer_entity.dart │ │ │ ├── topic_repository.dart │ │ │ └── topic_repository_impl.dart │ │ ├── topic.dart │ │ └── usecases │ │ │ ├── get_cached_skills_use_case.dart │ │ │ ├── get_topic_qnas_use_case.dart │ │ │ ├── get_wrong_answers_use_case.dart │ │ │ └── update_wrong_answer_use_case.dart │ ├── user │ │ ├── data_source │ │ │ ├── local │ │ │ │ ├── boxes │ │ │ │ │ ├── user_box.dart │ │ │ │ │ └── user_box.g.dart │ │ │ │ ├── user_local_data_source.dart │ │ │ │ └── user_local_data_source_impl.dart │ │ │ └── remote │ │ │ │ ├── fire_storage_user_ref.dart │ │ │ │ ├── models │ │ │ │ ├── bookmarked_youtube_content_model.dart │ │ │ │ ├── bookmarked_youtube_content_model.g.dart │ │ │ │ ├── marked_common_question_model.dart │ │ │ │ ├── marked_common_question_model.g.dart │ │ │ │ ├── uploaded_youtube_content_model.dart │ │ │ │ ├── uploaded_youtube_content_model.g.dart │ │ │ │ ├── user_model.dart │ │ │ │ ├── user_model.g.dart │ │ │ │ ├── watched_youtube_content_model.dart │ │ │ │ └── watched_youtube_content_model.g.dart │ │ │ │ ├── user_remote_data_source.dart │ │ │ │ ├── user_remote_data_source_impl.dart │ │ │ │ └── users_ref.dart │ │ ├── repositories │ │ │ ├── entities │ │ │ │ └── user_entity.dart │ │ │ ├── user_repository.dart │ │ │ └── user_repository_impl.dart │ │ ├── usecases │ │ │ ├── check_nickname_duplication.dart │ │ │ ├── create_user_use_case.dart │ │ │ ├── disable_review_available_state_use_case.dart │ │ │ ├── edit_user_profile_use_case.dart │ │ │ ├── get_user_use_case.dart │ │ │ ├── increase_completed_interview_count_use_case.dart │ │ │ ├── resign_user_info_use_case.dart │ │ │ ├── sotre_user_local_info_use_case.dart │ │ │ ├── update_last_login_date_use_cae.dart │ │ │ └── update_user_use_case.dart │ │ └── user.dart │ └── youtube │ │ ├── data_source │ │ └── remote │ │ │ ├── models │ │ │ ├── channel_model.dart │ │ │ ├── channel_model.g.dart │ │ │ ├── paragraph_model.dart │ │ │ ├── paragraph_model.g.dart │ │ │ ├── summary_model.dart │ │ │ ├── summary_model.g.dart │ │ │ ├── youtube_detail_model.dart │ │ │ ├── youtube_detail_model.g.dart │ │ │ ├── youtube_main_entity.dart │ │ │ ├── youtube_main_model.dart │ │ │ ├── youtube_main_model.g.dart │ │ │ ├── youtube_qna_model.dart │ │ │ └── youtube_qna_model.g.dart │ │ │ ├── youtube_ref.dart │ │ │ ├── youtube_remote_data_source.dart │ │ │ └── youtube_remote_data_source_impl.dart │ │ ├── index.dart │ │ ├── repositories │ │ ├── entities │ │ │ ├── caption_entity.dart │ │ │ ├── channel_detail_entity.dart │ │ │ ├── channel_entity.dart │ │ │ ├── paragraph_entity.dart │ │ │ ├── paragraph_entity.g.dart │ │ │ ├── summary_entity.dart │ │ │ ├── summary_entity.g.dart │ │ │ ├── video_overview_entity.dart │ │ │ ├── youtube_ai_main_theme_response.dart │ │ │ ├── youtube_ai_qna_response.dart │ │ │ ├── youtube_ai_summary_response_entity.dart │ │ │ ├── youtube_core_video_entity.dart │ │ │ └── youtube_video_entity.dart │ │ ├── enums │ │ │ └── youtube_content_analyzed_type.dart │ │ ├── youtube_repository.dart │ │ ├── youtube_repository_impl.dart │ │ └── youtube_repository_impl_internal.p.dart │ │ └── usecases │ │ ├── analyze_youtube_use_case.dart │ │ ├── enums │ │ └── youtube_upload_failed_type.dart │ │ ├── exception │ │ └── youtube_upload_exception.dart │ │ ├── get_main_summary_theme_use_case.dart │ │ ├── get_qnas_from_youtube_content_use_case.dart │ │ ├── get_remain_summary_form_youtube_content_use_case.dart │ │ ├── get_skill_ids_from_youtube_content_use_case.dart │ │ ├── get_summary_from_youtube_content_use_case.dart │ │ ├── get_youtube_overview_list_use_case.dart │ │ ├── get_youtube_video_data_use_case.dart │ │ └── qnas.json └── presentation │ ├── app.dart │ ├── pages │ ├── blog │ │ ├── blog_main │ │ │ ├── blog_main_event.dart │ │ │ ├── blog_main_page.dart │ │ │ ├── blog_main_state.dart │ │ │ ├── provider │ │ │ │ ├── blog_category_provider.dart │ │ │ │ ├── blog_content_pagination_provider.dart │ │ │ │ └── blog_content_pagination_provider.g.dart │ │ │ └── widgets │ │ │ │ ├── category_slider_bar.p.dart │ │ │ │ ├── content_list_view.p.dart │ │ │ │ └── scaffold.p.dart │ │ └── blog_origin_page.dart │ │ │ └── blog_origin_page.dart │ ├── home │ │ ├── home_event.dart │ │ ├── home_page.dart │ │ ├── internal_home_event.p.dart │ │ └── widgets │ │ │ ├── cheer_up_message_card.dart │ │ │ ├── common_interview_card.dart │ │ │ ├── home_state.dart │ │ │ ├── interview_indicator_card.dart │ │ │ ├── new_feature_card.p.dart │ │ │ ├── proficiency_interview_card.dart │ │ │ ├── resume_interview_card.dart │ │ │ ├── youtube_content_feature_card.p.dart │ │ │ └── youtube_interview_card.p.dart │ ├── interview │ │ ├── chat │ │ │ ├── chat_event.dart │ │ │ ├── chat_page.dart │ │ │ ├── chat_state.dart │ │ │ ├── constant │ │ │ │ └── recrod_progress_state.dart │ │ │ ├── providers │ │ │ │ ├── chat_async_adapter_provider.dart │ │ │ │ ├── chat_async_adapter_provider.g.dart │ │ │ │ ├── chat_message_history_internal_event.p.dart │ │ │ │ ├── chat_message_history_provider.dart │ │ │ │ ├── chat_message_history_provider.g.dart │ │ │ │ ├── chat_qnas_provider.dart │ │ │ │ ├── chat_qnas_provider.g.dart │ │ │ │ ├── chat_scroll_controller.dart │ │ │ │ ├── chat_scroll_controller.g.dart │ │ │ │ ├── common_type_chat_message_history_internal_event.p.dart │ │ │ │ ├── interview_progress_state_provider.dart │ │ │ │ ├── interview_progress_state_provider.g.dart │ │ │ │ ├── interview_result_page_view_controller_provider.dart │ │ │ │ ├── interview_result_page_view_controller_provider.g.dart │ │ │ │ ├── is_follow_up_process_active_provider.dart │ │ │ │ ├── is_follow_up_process_active_provider.g.dart │ │ │ │ ├── main_input_controller_provider.dart │ │ │ │ ├── main_input_controller_provider.g.dart │ │ │ │ ├── one_line_feedback_provider.dart │ │ │ │ ├── one_line_feedback_provider.g.dart │ │ │ │ ├── proficiency_type_chat_message_history_internal_event.p.dart │ │ │ │ ├── recognized_text_provider.dart │ │ │ │ ├── recognized_text_provider.g.dart │ │ │ │ ├── recommended_youtube_video_provider.dart │ │ │ │ ├── recommended_youtube_video_provider.g.dart │ │ │ │ ├── resume_type_chat_message_history_internal_event.p.dart │ │ │ │ ├── selected_chat_room_provider.dart │ │ │ │ ├── selected_chat_room_provider.g.dart │ │ │ │ ├── speech_mode_provider.dart │ │ │ │ ├── speech_mode_provider.g.dart │ │ │ │ ├── speech_to_text_provider.dart │ │ │ │ └── youtube_type_chat_message_history_internal_event.p.dart │ │ │ └── widgets │ │ │ │ ├── bubble.dart │ │ │ │ ├── chat_page_app_bar.p.dart │ │ │ │ ├── chat_page_scaffold.dart │ │ │ │ ├── chat_page_watch_view.p.dart │ │ │ │ ├── gradient_shine_effect_view.dart │ │ │ │ ├── interview_result │ │ │ │ ├── interview_induction_view.p.dart │ │ │ │ ├── interview_result_dialog.dart │ │ │ │ ├── one_line_review_view.p.dart │ │ │ │ └── pass_or_fail_view.p.dart │ │ │ │ ├── interview_tab_view │ │ │ │ ├── bottom_input_field.dart │ │ │ │ ├── bottom_speech_to_text_field.dart │ │ │ │ ├── bubble_indicator.dart │ │ │ │ ├── horizon_roating_dots.dart │ │ │ │ ├── interview_tab_view.dart │ │ │ │ ├── rounded_mic_motion_view.dart │ │ │ │ └── staggered_dot_wave.dart │ │ │ │ ├── qna_detail_box.dart │ │ │ │ ├── qna_expansion_tile.dart │ │ │ │ └── qna_tab_view.dart │ │ ├── chat_list │ │ │ ├── chat_list_event.dart │ │ │ ├── chat_list_page.dart │ │ │ ├── chat_list_state.dart │ │ │ ├── local_widgets │ │ │ │ └── chat_room_item_view.dart │ │ │ └── providers │ │ │ │ ├── chat_list_route_arg.dart │ │ │ │ ├── chat_list_route_arg.g.dart │ │ │ │ ├── interview_rooms_provider.dart │ │ │ │ ├── interview_rooms_provider.g.dart │ │ │ │ ├── practical_chat_room_list_provider.dart │ │ │ │ └── practical_chat_room_list_provider.g.dart │ │ ├── created_question_list │ │ │ ├── constant │ │ │ │ └── created_question_list_route_arg.dart │ │ │ ├── created_question_list_event.dart │ │ │ ├── created_question_list_page.dart │ │ │ ├── created_question_list_state.dart │ │ │ └── provider │ │ │ │ ├── created_question_list_rout_arg_provider.dart │ │ │ │ ├── created_question_list_rout_arg_provider.g.dart │ │ │ │ ├── listed_selectable_qnas_provider.dart │ │ │ │ └── listed_selectable_qnas_provider.g.dart │ │ ├── interview_level_selection │ │ │ ├── constant │ │ │ │ └── interview_level_selection_route_arg.dart │ │ │ ├── interview_level_selection_event.dart │ │ │ ├── interview_level_selection_page.dart │ │ │ ├── interview_level_selection_state.dart │ │ │ ├── provider │ │ │ │ ├── interview_level_selection_route_arg_provider.dart │ │ │ │ ├── interview_level_selection_route_arg_provider.g.dart │ │ │ │ ├── level_selection_page_view_controller.dart │ │ │ │ └── level_selection_page_view_controller.g.dart │ │ │ └── widgets │ │ │ │ ├── leading_view.p.dart │ │ │ │ ├── level_indicator_page_view.p.dart │ │ │ │ ├── level_selection_btns.p.dart │ │ │ │ └── scaffold.p.dart │ │ ├── proficiency_interview_topic_selection │ │ │ ├── constant │ │ │ │ └── proficiency_interview_topic_selection_route_arg.dart │ │ │ ├── proficiency_interview_topic_selection_event.dart │ │ │ ├── proficiency_interview_topic_selection_page.dart │ │ │ ├── proficiency_interview_topic_selection_state.dart │ │ │ ├── provider │ │ │ │ ├── proficiency_interview_topic_selection_route_arg_provide.dart │ │ │ │ ├── proficiency_interview_topic_selection_route_arg_provide.g.dart │ │ │ │ ├── selected_tech_sets_provider.dart │ │ │ │ └── selected_tech_sets_provider.g.dart │ │ │ └── widgets │ │ │ │ ├── leading_view.p.dart │ │ │ │ ├── recommended_tech_set_view.p.dart │ │ │ │ ├── scaffold.p.dart │ │ │ │ ├── search_bar.p.dart │ │ │ │ └── selected_tech_set_list_view.p.dart │ │ ├── question_count_select │ │ │ ├── constant │ │ │ │ └── select_question_count_route_argument.dart │ │ │ ├── providers │ │ │ │ ├── select_question_count_route_arg.dart │ │ │ │ ├── select_question_count_route_arg.g.dart │ │ │ │ ├── selected_question_count_provider.dart │ │ │ │ └── selected_question_count_provider.g.dart │ │ │ ├── question_count_select_event.dart │ │ │ ├── question_count_select_page.dart │ │ │ └── question_count_select_state.dart │ │ ├── question_creation │ │ │ ├── constant │ │ │ │ └── question_creation_route_arg.dart │ │ │ ├── provider │ │ │ │ ├── created_proficiency_qnas_provider.dart │ │ │ │ ├── created_proficiency_qnas_provider.g.dart │ │ │ │ ├── question_creation_route_arg_provider.dart │ │ │ │ └── question_creation_route_arg_provider.g.dart │ │ │ ├── question_creation_event.dart │ │ │ ├── question_creation_page.dart │ │ │ ├── question_creation_state.dart │ │ │ └── widgets │ │ │ │ ├── app_bar.p.dart │ │ │ │ ├── bottom_fixed_button.p.dart │ │ │ │ ├── illust_view.p.dart │ │ │ │ ├── leading_view.p.dart │ │ │ │ └── scaffold.p.dart │ │ ├── select_common_question │ │ │ ├── constant │ │ │ │ └── select_common_question_route_arg.dart │ │ │ ├── provider │ │ │ │ ├── select_common_question_route_arg_provider.dart │ │ │ │ ├── select_common_question_route_arg_provider.g.dart │ │ │ │ ├── selectable_common_qnas_provider.dart │ │ │ │ ├── selectable_common_qnas_provider.g.dart │ │ │ │ ├── selected_interview_topic_provider.dart │ │ │ │ └── selected_interview_topic_provider.g.dart │ │ │ ├── select_common_question_event.dart │ │ │ ├── select_common_question_page.dart │ │ │ ├── select_common_question_state.dart │ │ │ └── widgets │ │ │ │ ├── tech_set_header.dart │ │ │ │ └── tech_set_page.dart │ │ ├── select_commotion_interview_type │ │ │ ├── selected_common_interview_type_event.dart │ │ │ └── selected_common_interview_type_page.dart │ │ └── topic_select │ │ │ ├── interview_topic_select_event.dart │ │ │ ├── interview_topic_select_page.dart │ │ │ ├── interview_topic_select_state.dart │ │ │ └── providers │ │ │ ├── interview_topic_select_route_arg.dart │ │ │ ├── interview_topic_select_route_arg.g.dart │ │ │ ├── interview_topic_select_scroll_controller_provider.dart │ │ │ ├── interview_topic_select_scroll_controller_provider.g.dart │ │ │ ├── selected_interview_topics_provider.dart │ │ │ └── selected_interview_topics_provider.g.dart │ ├── main │ │ ├── main_event.dart │ │ ├── main_page.dart │ │ ├── main_state.dart │ │ └── provider │ │ │ ├── show_new_feature_indicator_provider.dart │ │ │ └── show_new_feature_indicator_provider.g.dart │ ├── my_info │ │ ├── job_group_setting │ │ │ ├── job_group_setting_event.dart │ │ │ ├── job_group_setting_page.dart │ │ │ ├── job_group_setting_state.dart │ │ │ └── provider │ │ │ │ ├── selected_job_groups_provider.dart │ │ │ │ └── selected_job_groups_provider.g.dart │ │ ├── my_page │ │ │ ├── my_page.dart │ │ │ ├── my_page_event.dart │ │ │ ├── my_page_state.dart │ │ │ └── widgets │ │ │ │ ├── additional_info_card.dart │ │ │ │ ├── card_list_tile_button.dart │ │ │ │ ├── expandable_skill_wrapped_list_view.dart │ │ │ │ ├── expandable_wrapped_list_view.dart │ │ │ │ ├── intro_view.dart │ │ │ │ ├── my_activity_card.p.dart │ │ │ │ ├── my_info_page_scaffold.dart │ │ │ │ ├── setting_card.dart │ │ │ │ └── user_info_card.dart │ │ ├── my_youtube_board │ │ │ ├── constant │ │ │ │ └── youtube_board_tab_type.enum.dart │ │ │ ├── my_youtube_board_event.dart │ │ │ ├── my_youtube_board_page.dart │ │ │ ├── my_youtube_board_state.dart │ │ │ ├── provider │ │ │ │ ├── bookmarked_paging_controller_provider.dart │ │ │ │ ├── bookmarked_paging_controller_provider.g.dart │ │ │ │ ├── show_upload_floating_button_provider.dart │ │ │ │ ├── show_upload_floating_button_provider.g.dart │ │ │ │ ├── uploaded_history_paging_controller_provider.dart │ │ │ │ ├── uploaded_history_paging_controller_provider.g.dart │ │ │ │ ├── watched_history_paging_controller_provider.dart │ │ │ │ └── watched_history_paging_controller_provider.g.dart │ │ │ └── widgets │ │ │ │ ├── bookmark_animated_deletable_list_item.p.dart │ │ │ │ ├── bookmarked_tab_view.p.dart │ │ │ │ ├── scaffold.p.dart │ │ │ │ ├── tab_bar.p.dart │ │ │ │ ├── uploaded_content_tab_view.p.dart │ │ │ │ └── watched_history_tab_view.p.dart │ │ ├── profile_setting │ │ │ ├── profile_setting_event.dart │ │ │ ├── profile_setting_page.dart │ │ │ ├── profile_setting_state.dart │ │ │ ├── providers │ │ │ │ ├── picked_profile_img.dart │ │ │ │ ├── picked_profile_img.g.dart │ │ │ │ ├── profile_setting_route_arg_provider.dart │ │ │ │ └── profile_setting_route_arg_provider.g.dart │ │ │ └── widgets │ │ │ │ ├── nickname_input_field.dart │ │ │ │ ├── profile_img_button.dart │ │ │ │ └── save_button.dart │ │ └── skill_setting │ │ │ ├── providers │ │ │ ├── searched_skills_provider.dart │ │ │ ├── searched_skills_provider.g.dart │ │ │ ├── selected_skills_provider.dart │ │ │ ├── selected_skills_provider.g.dart │ │ │ └── skill_setting_state.dart │ │ │ ├── skill_setting_event.dart │ │ │ └── skill_setting_page.dart │ ├── sign_in │ │ ├── sign_in_event.dart │ │ ├── sign_in_page.dart │ │ └── widgets │ │ │ ├── apple_sign_in_button.dart │ │ │ └── google_sign_in_button.dart │ ├── sign_up │ │ ├── events │ │ │ ├── job_group_step_event.p.dart │ │ │ ├── nickname_step_event.p.dart │ │ │ ├── sign_up_event.dart │ │ │ └── skill_step_event.p.dart │ │ ├── providers │ │ │ ├── sign_up_jobs_provider.dart │ │ │ ├── sign_up_jobs_provider.g.dart │ │ │ ├── sign_up_step_controller.dart │ │ │ ├── sign_up_step_controller.g.dart │ │ │ ├── sign_up_topics_provider.dart │ │ │ └── sign_up_topics_provider.g.dart │ │ ├── sign_up_page.dart │ │ ├── sign_up_state.dart │ │ ├── steps │ │ │ ├── job_group_select_step.dart │ │ │ ├── nickname_input_step.dart │ │ │ └── skill_select_step.dart │ │ └── widgets │ │ │ ├── select_result_chip_list_view.dart │ │ │ ├── sign_up_app_bar.dart │ │ │ └── sign_up_step_intro_message.dart │ ├── splash │ │ ├── splash_event.dart │ │ └── splash_page.dart │ ├── study │ │ ├── learning │ │ │ ├── learning_detail_event.dart │ │ │ ├── learning_detail_page.dart │ │ │ ├── providers │ │ │ │ ├── current_study_qna_index_provider.dart │ │ │ │ ├── current_study_qna_index_provider.g.dart │ │ │ │ ├── study_answer_blur_provider.dart │ │ │ │ ├── study_answer_blur_provider.g.dart │ │ │ │ ├── study_bookmark_filter_provider.dart │ │ │ │ ├── study_bookmark_filter_provider.g.dart │ │ │ │ └── study_qna_controller.dart │ │ │ └── widgets │ │ │ │ ├── entire_question_list_view.dart │ │ │ │ ├── learning_detail_bottom_controller_bar.dart │ │ │ │ ├── learning_detail_page_app_bar.dart │ │ │ │ ├── learning_detail_state.dart │ │ │ │ ├── study_progress_indicator.dart │ │ │ │ └── study_qna_view.dart │ │ └── topic_selection │ │ │ ├── providers │ │ │ ├── selected_study_topic_provider.dart │ │ │ ├── selected_study_topic_provider.g.dart │ │ │ ├── study_topic_selection_scroll_controller.dart │ │ │ ├── study_topic_selection_scroll_controller.g.dart │ │ │ └── study_topic_selection_state.dart │ │ │ ├── study_topic_selection_event.dart │ │ │ ├── study_topic_selection_page.dart │ │ │ └── widgets │ │ │ ├── scaffold.p.dart │ │ │ ├── study_topic_grid_view.p.dart │ │ │ └── wrong_answer_card.p.dart │ ├── wrong_answer_note │ │ ├── local_widgets │ │ │ ├── review_note_detail_app_bar.dart │ │ │ ├── review_note_detail_bottom_controller_bar.dart │ │ │ ├── review_note_qna_list_tile.dart │ │ │ ├── wrong_answer_empty_list_placeholder.dart │ │ │ ├── wrong_answer_floating_action_btn.dart │ │ │ └── wrong_answer_header.dart │ │ ├── providers │ │ │ ├── review_note_detail_page_controller.dart │ │ │ ├── review_note_detail_page_controller.g.dart │ │ │ ├── selected_wrong_answer_topic_provider.dart │ │ │ ├── selected_wrong_answer_topic_provider.g.dart │ │ │ ├── wrong_answer_blur_provider.dart │ │ │ ├── wrong_answer_blur_provider.g.dart │ │ │ ├── wrong_answer_note_scroll_controller.dart │ │ │ ├── wrong_answer_note_scroll_controller.g.dart │ │ │ ├── wrong_answers_provider.dart │ │ │ └── wrong_answers_provider.g.dart │ │ ├── wrong_answer_detail_page.dart │ │ ├── wrong_answer_note_event.dart │ │ ├── wrong_answer_note_page.dart │ │ └── wrong_answer_note_state.dart │ └── youtube │ │ ├── channel_detail │ │ ├── channel_detail_event.dart │ │ ├── channel_detail_page.dart │ │ ├── channel_detail_state.dart │ │ ├── provider │ │ │ ├── channel_contents_pagination_provider.dart │ │ │ ├── channel_contents_pagination_provider.g.dart │ │ │ ├── channel_detail_provider.dart │ │ │ ├── channel_detail_provider.g.dart │ │ │ ├── channel_detail_route_arg_provider.dart │ │ │ └── channel_detail_route_arg_provider.g.dart │ │ └── widgets │ │ │ ├── channel_info_view.p.dart │ │ │ ├── content_grid_view.p.dart │ │ │ └── scaffold.p.dart │ │ ├── detail │ │ ├── constant │ │ │ └── youtube_play_state.enum.dart │ │ ├── providers │ │ │ ├── fab_scroll_expose_provider.dart │ │ │ ├── is_bookmark_checked_provider.dart │ │ │ ├── is_bookmark_checked_provider.g.dart │ │ │ ├── is_interview_progress_ready_provider.dart │ │ │ ├── is_interview_progress_ready_provider.g.dart │ │ │ ├── related_youtube_videos_provider.dart │ │ │ ├── related_youtube_videos_provider.g.dart │ │ │ ├── selected_youtube_qnas_provider.dart │ │ │ ├── youtube_content_qna_provider.dart │ │ │ ├── youtube_content_qna_provider.g.dart │ │ │ ├── youtube_detail_route_arg_provider.dart │ │ │ ├── youtube_detail_route_arg_provider.g.dart │ │ │ ├── youtube_main_info_provider.dart │ │ │ ├── youtube_main_info_provider.g.dart │ │ │ ├── youtube_player_provider.dart │ │ │ ├── youtube_summary_provider.dart │ │ │ ├── youtube_summary_provider.g.dart │ │ │ ├── youtube_video_data_provider.dart │ │ │ └── youtube_video_data_provider.g.dart │ │ ├── widgets │ │ │ ├── app_bar.p.dart │ │ │ ├── bottom_floating_View.p.dart │ │ │ ├── constants │ │ │ │ └── contents_detail_tab_type.enum.dart │ │ │ ├── content_info_view.p.dart │ │ │ ├── interview_tab_view.p.dart │ │ │ ├── scaffold.p.dart │ │ │ ├── section_title.dart │ │ │ ├── selectable_qna_box.dart │ │ │ ├── summary_note_foldable_item.dart │ │ │ ├── summary_tab_view.p.dart │ │ │ ├── tab_bar.p.dart │ │ │ └── youtube_player_place_holder.p.dart │ │ ├── youtube_detail_event.dart │ │ ├── youtube_detail_page.dart │ │ └── youtube_detail_state.dart │ │ ├── main │ │ ├── provider │ │ │ ├── selected_filter_category_provider.dart │ │ │ ├── youtube_content_pagination_provider.dart │ │ │ └── youtube_content_pagination_provider.g.dart │ │ ├── widgets │ │ │ ├── category_slider_bar.p.dart │ │ │ ├── content_list_view.p.dart │ │ │ ├── scaffold.p.dart │ │ │ └── youtube_pagination_indicator_view.dart │ │ ├── youtube_main_event.dart │ │ ├── youtube_main_page.dart │ │ └── youtube_main_state.dart │ │ ├── upload │ │ ├── analyze_youtube │ │ │ ├── analyze_youtube_page.dart │ │ │ ├── analyze_youtube_progerss_state.dart │ │ │ └── analyze_youtube_progress_event.dart │ │ ├── submitted_youtube_confirm │ │ │ ├── provider │ │ │ │ ├── submitted_youtube_confirm_arg_provider.dart │ │ │ │ ├── submitted_youtube_confirm_arg_provider.g.dart │ │ │ │ ├── submitted_youtube_info_provider.dart │ │ │ │ └── submitted_youtube_info_provider.g.dart │ │ │ ├── submitted_youtube_confirm_event.dart │ │ │ ├── submitted_youtube_confirm_page.dart │ │ │ └── submitted_youtube_confirm_state.dart │ │ └── youtube_link_submit │ │ │ ├── provider │ │ │ ├── youtube_link_input_controller_provider.dart │ │ │ ├── youtube_link_input_controller_provider.g.dart │ │ │ └── youtube_link_submit_state.dart │ │ │ ├── youtube_link_submit_event.dart │ │ │ └── youtube_link_submit_page.dart │ │ └── upload_failed │ │ ├── provider │ │ ├── youtube_upload_failed_route_arg_provider.dart │ │ └── youtube_upload_failed_route_arg_provider.g.dart │ │ ├── youtube_upload_fail_page.dart │ │ └── youtube_upload_failed_event.dart │ ├── providers │ ├── input │ │ ├── nickname_input_provider.dart │ │ ├── nickname_input_provider.g.dart │ │ ├── skill_text_field_controller_provider.dart │ │ └── skill_text_field_controller_provider.g.dart │ ├── main_bottom_navigation_provider.dart │ ├── main_bottom_navigation_provider.g.dart │ ├── scroll │ │ ├── selected_job_group_scroll_controller.dart │ │ ├── selected_job_group_scroll_controller.g.dart │ │ ├── selected_skill_scroll_controller.dart │ │ └── selected_skill_scroll_controller.g.dart │ ├── system │ │ ├── app_version_provider.dart │ │ ├── app_version_provider.g.dart │ │ ├── detect_network_connectivity_provider.dart │ │ ├── detect_network_connectivity_provider.g.dart │ │ ├── notification_status_provider.dart │ │ └── notification_status_provider.g.dart │ ├── topic │ │ ├── selectable_common_qnas_provider.dart │ │ ├── selectable_common_qnas_provider.g.dart │ │ ├── selectable_qnas_provider.dart │ │ ├── selectable_qnas_provider.g.dart │ │ ├── sorted_topics_provider.dart │ │ ├── sorted_topics_provider.g.dart │ │ └── study_qna_controller.g.dart │ └── user │ │ ├── user_auth_provider.dart │ │ ├── user_auth_provider.g.dart │ │ ├── user_info_provider.dart │ │ ├── user_info_provider.g.dart │ │ ├── user_topics_provider.dart │ │ └── user_topics_provider.g.dart │ └── widgets │ ├── base │ ├── base_new_page.dart │ ├── base_page.dart │ ├── base_statless_page.dart │ ├── base_view.dart │ ├── controller_holder.dart │ └── index.dart │ ├── common │ ├── animated │ │ ├── animated_appear_view.dart │ │ └── animated_size_and_fade.dart │ ├── app_bar │ │ ├── animated_app_bar.dart │ │ ├── back_button_app_bar.dart │ │ ├── foldable_app_bar.dart │ │ └── techtalk_app_bar.dart │ ├── avatar │ │ └── clip_oval_circle_avatar.dart │ ├── bottom_sheet │ │ ├── bottom_sheet_intent.dart │ │ └── option_list_bottom_sheet.dart │ ├── box │ │ ├── async_skeleton_widget_builder.dart │ │ ├── empty_box.dart │ │ ├── filled_text_box.dart │ │ ├── scroll_cameleon_box.dart │ │ └── skeleton_box.dart │ ├── button │ │ ├── all_button.dart │ │ ├── app_back_button.dart │ │ ├── book_mark_button.dart │ │ ├── icon_flash_area_button.dart │ │ ├── reset_button.dart │ │ ├── rounded_outlined_button.dart │ │ ├── see_all_question_button.dart │ │ └── under_label_icon_button.dart │ ├── checkbox │ │ └── techtalk_checkbox.dart │ ├── chip │ │ ├── closable_rect_filled_chip.dart │ │ ├── closable_skill_filled_chip.dart │ │ ├── dark_tranparent_chip.dart │ │ ├── label_chip.dart │ │ ├── normal_rounded_chip.dart │ │ ├── outlined_chip.dart │ │ ├── resume_question_type_chip.dart │ │ ├── rounded_filled_chip.dart │ │ ├── rounded_outlined_chip.dart │ │ ├── rounded_skill_filled_chip.dart │ │ ├── selectable_category_chip.dart │ │ ├── selectable_chip.dart │ │ └── tech_set_filled_chip.dart │ ├── common.dart │ ├── constant │ │ └── content_filter_category.dart │ ├── delegate │ │ └── sticky_container.dart │ ├── dialog │ │ └── app_dialog.dart │ ├── divider │ │ └── list_view_divider.dart │ ├── grid_view │ │ └── expandable_youtube_content_grid_view.dart │ ├── image │ │ ├── round_profile_image.dart │ │ ├── rounded_skill_image.dart │ │ └── thumbnail_image_view.dart │ ├── indicator │ │ ├── exception_indicator.dart │ │ ├── frequently_wrong_answer_indicator.dart │ │ ├── interview_count_result_indicator.dart │ │ ├── new_badge.dart │ │ ├── response_indicator.dart │ │ └── tecktalk_refresh_indicator.dart │ ├── input │ │ ├── flat_switch.dart │ │ ├── techtalk_text_field.dart │ │ └── under_validate_text_field.dart │ ├── item │ │ ├── blog_content_item_view.dart │ │ ├── youtube_content_item_view.dart │ │ └── youtube_content_small_item_view.dart │ ├── layout │ │ └── mobie_layout_constraint_layout.dart │ ├── list_view │ │ └── tech_set_list_view.dart │ ├── state │ │ └── keep_alive_view.dart │ ├── tab_bar │ │ └── techtalk_tab_bar.dart │ ├── text │ │ └── bullet_text.dart │ ├── tile │ │ ├── flexible_expansion_tile.dart │ │ └── job_group_list_tile.dart │ └── toast │ │ └── app_toast.dart │ └── section │ ├── interview_topic_card.dart │ ├── job_group_selection_scaffold.dart │ ├── job_group_sliver_list_view.dart │ ├── searched_skill_list_view.dart │ ├── selected_job_group_list_view_delegate.dart │ ├── skill_selection_scaffold.dart │ ├── study_topic_card.dart │ └── tech_selection_bottom_sheet │ ├── provider │ ├── tech_selection_bottom_sheet_resource_provider.dart │ ├── tech_set_selection_bottom_sheet_route_arg_provider.dart │ └── tech_set_selection_bottom_sheet_route_arg_provider.g.dart │ ├── tech_selection_bottom_sheet_event.dart │ ├── tech_selection_bottom_sheet_state.dart │ ├── tech_set_selection_bottom_sheet.dart │ └── widgets │ ├── header_selection_view.p.dart │ ├── job_group_page_view.p.dart │ ├── scaffold.p.dart │ ├── searched_skill_list_view.p.dart │ ├── selected_tech_set_list_view.p.dart │ └── skill_page_view.p.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.github/ISSUE_TEMPLATE/backlog-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Backlog request 3 | about: manage backlog 4 | title: "[섹션] 내용" 5 | labels: "backlog \U0001F51C" 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🏷️ 요약 11 | 12 | 13 |
14 | 15 | ## 📄 명세 16 | 17 | - [ ] somthing 18 | 19 | 20 |
21 | 22 | 23 | ## 👷 작업자 24 | 25 | 26 | 27 |
28 | 29 | ## 💬 기타 30 | 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/discussion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Discussion 3 | about: Have a discussion about programming agreements 4 | title: "[섹션] 논의에 대한 설명" 5 | labels: '' 6 | assignees: Xim-ya 7 | 8 | --- 9 | 10 | # 🤝 논의사항 11 | 12 | 13 |
14 | 15 | ## 📝 상세 내용 16 | 17 | 18 |
19 | 20 | ## 🚀 기대 효과 21 | 22 | 23 |
24 | 25 | ## 👷 참가자 26 | 27 | 28 |
29 | 30 | ## 💬 기타 31 | 32 | 33 |
34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: 'Suggest an idea for this project ' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🚀 기능 11 | 12 | 13 | ## 📸 재현 이미지 또는 코드 샘플 14 | 15 | 16 | 17 | ## 📄 기능 명세 18 | 19 | 20 | ## 👷 작업자 21 | 22 | 23 | ## 💬 기타 24 | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## 📝 변경 내용 3 | * 4 | 5 |
6 | 7 | ## 🔍 변경 이유 8 | * 9 | 10 |
11 | 12 | ## 🌍 영향 13 | * 14 | 15 |
16 | 17 | ## 🧪 테스트 계획 18 | * 19 | 20 |
21 | 22 | ## 👥 리뷰어 23 | * 24 | 25 |
26 | 27 | ## 📌 기타 사항 28 | * 29 | 30 |
31 | -------------------------------------------------------------------------------- /.vscode/keybindings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | // Command + D를 눌렀을 때 현재 커서 위치의 코드를 드래그 선택 4 | "key": "cmd+d", 5 | "command": "editor.action.duplicateSelection", 6 | "when": "editorTextFocus && !editorHasSelection" 7 | } 8 | ] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/dev/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/techtalk/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.techtalk.ai 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-en/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TechTalk 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 테크톡 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/locales_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /android/app/src/prod/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/prod/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/prod/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/prod/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/android/app/src/prod/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/prod/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /assets/character/blue_04.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/character/green_04.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/fonts/pretendard/Pretendard-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/fonts/pretendard/Pretendard-Bold.otf -------------------------------------------------------------------------------- /assets/fonts/pretendard/Pretendard-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/fonts/pretendard/Pretendard-Medium.otf -------------------------------------------------------------------------------- /assets/fonts/pretendard/Pretendard-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/fonts/pretendard/Pretendard-Regular.otf -------------------------------------------------------------------------------- /assets/fonts/pretendard/Pretendard-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/fonts/pretendard/Pretendard-SemiBold.otf -------------------------------------------------------------------------------- /assets/icons/alarm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/ar_up_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_down_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_left_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_right_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_up_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/boomark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/bullet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/chat_bubble_tale.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/check_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/circle_small_close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/close_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/common_interview_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/core_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/correct.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/dark_check_box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/delete_or_wrong.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/expansion_arrow_indicator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/icon_app_bar_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/incorrect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/menu_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/more_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/new_right_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/note.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/plus_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/red_alert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/red_warnning_big.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/right_aligned_right_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/round_blue_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_blue_exclamation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/rounded_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/rounded_check_small_blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/rounded_check_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/rounded_close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_close_blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_close_small_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_close_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_exclamation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/rounded_more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/rounded_plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/rounded_plus_big.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_send_inactive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/rounded_top.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/rounded_warnning_small_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/search_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/send_activate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/send_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/study.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/talker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/video_upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/wemo_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/youtube_interview_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/youtube_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/app_icon.png -------------------------------------------------------------------------------- /assets/images/avatar_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/avatar_1.png -------------------------------------------------------------------------------- /assets/images/blank_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/blank_profile.png -------------------------------------------------------------------------------- /assets/images/induction_practical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/induction_practical.png -------------------------------------------------------------------------------- /assets/images/induction_resume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/induction_resume.png -------------------------------------------------------------------------------- /assets/images/induction_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/induction_single.png -------------------------------------------------------------------------------- /assets/images/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/splash_image.png -------------------------------------------------------------------------------- /assets/images/topic_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_android.png -------------------------------------------------------------------------------- /assets/images/topic_data_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_data_structure.png -------------------------------------------------------------------------------- /assets/images/topic_database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_database.png -------------------------------------------------------------------------------- /assets/images/topic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_flutter.png -------------------------------------------------------------------------------- /assets/images/topic_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_ios.png -------------------------------------------------------------------------------- /assets/images/topic_java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_java.png -------------------------------------------------------------------------------- /assets/images/topic_javascript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_javascript.png -------------------------------------------------------------------------------- /assets/images/topic_nest_js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_nest_js.png -------------------------------------------------------------------------------- /assets/images/topic_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_network.png -------------------------------------------------------------------------------- /assets/images/topic_operating_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_operating_system.png -------------------------------------------------------------------------------- /assets/images/topic_react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_react.png -------------------------------------------------------------------------------- /assets/images/topic_spring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_spring.png -------------------------------------------------------------------------------- /assets/images/topic_swift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_swift.png -------------------------------------------------------------------------------- /assets/images/topic_webFrontend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/assets/images/topic_webFrontend.png -------------------------------------------------------------------------------- /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 | - provider: true -------------------------------------------------------------------------------- /icons_launcher.yaml: -------------------------------------------------------------------------------- 1 | icons_launcher: 2 | adaptive_icon_background : '#FFFFFF' 3 | image_path: "assets/images/app_icon.png" 4 | platforms: 5 | android: 6 | enable: true 7 | ios: 8 | enable: true 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Localization/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by 심야 on 8/1/24. 6 | 7 | */ 8 | "CFBundleDisplayName" = "TechTalk"; 9 | "NSPhotoLibraryUsageDescription" = "The app needs access to the photo library to change your profile image."; 10 | "NSMicrophoneUsageDescription" = "Please allow the app to access the microphone for voice recognition."; 11 | "NSSpeechRecognitionUsageDescription" = "Please allow the app to analyze speech for voice recognition."; 12 | 13 | -------------------------------------------------------------------------------- /ios/Localization/ko.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by 심야 on 8/1/24. 6 | 7 | */ 8 | "CFBundleDisplayName" = "테크톡"; 9 | "NSPhotoLibraryUsageDescription" = "프로필 이미지를 변경하려면 앱에서 사진 라이브러리에 액세스 할 수 있어야 합니다."; 10 | "NSMicrophoneUsageDescription" = "앱이 음성 인식을 위해 마이크에 접근할 수 있도록 허용해주세요."; 11 | "NSSpeechRecognitionUsageDescription" = "앱이 음성 인식을 위해 음성을 분석할 수 있도록 허용해주세요."; -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/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/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakeFrog/TechTalk/f055c8d4dde005cb898a74a035395d9518e9176b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.applesignin 8 | 9 | Default 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/en.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/en.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/ko.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:680296657690:ios:7aea3588a2b3c4a083b2a3", 5 | "FIREBASE_PROJECT_ID": "techtalk-prod-32", 6 | "GCM_SENDER_ID": "680296657690" 7 | } -------------------------------------------------------------------------------- /ios/firebase_app_id_file_dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:481445661271:ios:debd85030f995e31a27112", 5 | "FIREBASE_PROJECT_ID": "techtalk-dev-33", 6 | "GCM_SENDER_ID": "481445661271" 7 | } -------------------------------------------------------------------------------- /lib/app/di/feature_di_interface.dart: -------------------------------------------------------------------------------- 1 | abstract base class FeatureDependencyInjection { 2 | void init() { 3 | dataSources(); 4 | repositories(); 5 | useCases(); 6 | } 7 | 8 | void dataSources(); 9 | void repositories(); 10 | void useCases() {} 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/di/index.dart: -------------------------------------------------------------------------------- 1 | export 'feature_di_interface.dart'; 2 | export 'modules/topic_di.dart'; 3 | export 'modules/system_di.dart'; 4 | export 'modules/chat_di.dart'; 5 | export 'modules/user_di.dart'; 6 | export 'modules/auth_di.dart'; 7 | export 'modules/tech_set_di.dart'; 8 | export 'app_binding.dart'; -------------------------------------------------------------------------------- /lib/app/entrypoints/main_dev.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/environment/environment.enum.dart'; 2 | import 'package:techtalk/app/environment/flavor.dart'; 3 | import 'package:techtalk/presentation/app.dart'; 4 | 5 | void main() async { 6 | Flavor.initialize(Environment.dev); 7 | 8 | return runFlavoredApp(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/entrypoints/main_prod.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/environment/environment.enum.dart'; 2 | import 'package:techtalk/app/environment/flavor.dart'; 3 | import 'package:techtalk/presentation/app.dart'; 4 | 5 | Future main() async { 6 | Flavor.initialize(Environment.prod); 7 | 8 | return runFlavoredApp(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/environment/app_version.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/system/repositories/entities/version_entity.dart'; 2 | 3 | final class AppVersion { 4 | static final AppVersion _instance = AppVersion._internal(); 5 | 6 | factory AppVersion() => _instance; 7 | 8 | AppVersion._internal(); 9 | 10 | VersionEntity? to; 11 | 12 | void initialize(VersionEntity version) { 13 | to = version; 14 | } 15 | 16 | bool get isOnReview => to?.isOnReview ?? false; 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/localization/localization_enum.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum Localization { 4 | en(locale: Locale('en', 'US')), 5 | kr(locale: Locale('ko', 'KR')); 6 | 7 | final Locale locale; 8 | 9 | const Localization({required this.locale}); 10 | 11 | /// 12 | /// language 코드오 매칭된 locale을 반환해주는 코드입니다. 13 | /// 14 | static Localization getMatchedLocalization(String languageCode) { 15 | if (languageCode == Localization.kr.locale.languageCode) { 16 | return Localization.kr; 17 | } else { 18 | return Localization.en; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/localization/localization_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | abstract class LocalizationUtils { 5 | // context가 없는 곳에서 Localization을 적용해야 할 경우 사용 6 | static String localizeNoContext(BuildContext context, String jsonKey) { 7 | return jsonKey.tr(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/network/app_youtube_explode.dart: -------------------------------------------------------------------------------- 1 | import 'package:youtube_explode_dart/youtube_explode_dart.dart'; 2 | 3 | abstract final class AppYoutubeExplode { 4 | AppYoutubeExplode._internal(); 5 | 6 | static YoutubeExplode? _instance; 7 | 8 | static YoutubeExplode getInstance() => _instance ??= YoutubeExplode(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/notification/app_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_messaging/firebase_messaging.dart'; 2 | 3 | abstract class AppNotification { 4 | Future init() async { 5 | await FirebaseMessaging.instance 6 | .getInitialMessage() 7 | .then((RemoteMessage? message) { 8 | if (message != null) { 9 | if (message.notification != null) {} 10 | } 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/app/router/route_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:go_router/go_router.dart'; 2 | 3 | /// 4 | /// Gorouter extension 메소드 5 | /// 6 | extension GoRouterExtension on GoRouter { 7 | // 특정 경로까지 뒤로 이동 8 | void popUntilPath(String targetPath) { 9 | while (routerDelegate.currentConfiguration.matches.last.matchedLocation != 10 | targetPath) { 11 | if (!canPop()) { 12 | return; 13 | } 14 | pop(); 15 | } 16 | } 17 | 18 | // 여러 경로 중 하나에 도달할 때까지 뒤로 이동 19 | void popUntilMultiPath(List targetPaths) { 20 | print( 21 | '이지빵 : ${routerDelegate.currentConfiguration.matches.last.matchedLocation}'); 22 | while (!targetPaths.contains( 23 | routerDelegate.currentConfiguration.matches.last.matchedLocation)) { 24 | if (!canPop()) { 25 | return; 26 | } 27 | pop(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/style/index.dart: -------------------------------------------------------------------------------- 1 | export 'app_color.dart'; 2 | export 'app_text_style.dart'; 3 | export 'app_theme.dart'; 4 | -------------------------------------------------------------------------------- /lib/app/style/themes/app_bar_theme.dart: -------------------------------------------------------------------------------- 1 | part of '../app_theme.dart'; 2 | 3 | abstract class _AppBarTheme { 4 | static final light = AppBarTheme( 5 | systemOverlayStyle: SystemUiOverlayStyle.dark, 6 | color: AppColor().white, 7 | elevation: 0, 8 | scrolledUnderElevation: 0, 9 | centerTitle: false, 10 | titleTextStyle: AppTextStyle.headline2.copyWith( 11 | color: AppColor().black, 12 | ), 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/style/themes/filled_button_theme.dart: -------------------------------------------------------------------------------- 1 | part of '../app_theme.dart'; 2 | 3 | abstract class _FilledButtonTheme { 4 | static final light = FilledButtonThemeData( 5 | style: FilledButton.styleFrom( 6 | backgroundColor: AppColor().brand3, 7 | disabledBackgroundColor: AppColor().brand1, 8 | foregroundColor: AppColor().white, 9 | disabledForegroundColor: AppColor().white, 10 | elevation: 0, 11 | padding: const EdgeInsets.symmetric( 12 | horizontal: 36, 13 | vertical: 18, 14 | ), 15 | shape: RoundedRectangleBorder( 16 | borderRadius: BorderRadius.circular(16), 17 | ), 18 | textStyle: AppTextStyle.title1, 19 | ), 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/style/themes/input_decoration_theme.dart: -------------------------------------------------------------------------------- 1 | part of '../app_theme.dart'; 2 | 3 | final _roundedBorderWithoutLine = OutlineInputBorder( 4 | borderSide: BorderSide.none, 5 | borderRadius: BorderRadius.circular(16), 6 | ); 7 | 8 | abstract class _InputDecorationTheme { 9 | static final InputDecorationTheme light = InputDecorationTheme( 10 | contentPadding: const EdgeInsets.symmetric( 11 | vertical: 14, 12 | horizontal: 16, 13 | ), 14 | filled: true, 15 | fillColor: AppColor().background1, 16 | border: _roundedBorderWithoutLine, 17 | errorBorder: _roundedBorderWithoutLine, 18 | focusedBorder: _roundedBorderWithoutLine, 19 | focusedErrorBorder: _roundedBorderWithoutLine, 20 | hintStyle: AppTextStyle.body1.copyWith( 21 | color: AppColor().gray3, 22 | ), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/style/themes/outlined_button_theme.dart: -------------------------------------------------------------------------------- 1 | part of '../app_theme.dart'; 2 | 3 | abstract class _OutlinedButtonTheme { 4 | static final light = OutlinedButtonThemeData( 5 | style: FilledButton.styleFrom( 6 | backgroundColor: AppColor().white, 7 | disabledBackgroundColor: AppColor().white, 8 | foregroundColor: AppColor().brand3, 9 | disabledForegroundColor: AppColor().blue1, 10 | elevation: 0, 11 | side: BorderSide(color: AppColor().gray2), 12 | padding: const EdgeInsets.symmetric( 13 | horizontal: 36, 14 | vertical: 18, 15 | ), 16 | shape: RoundedRectangleBorder( 17 | borderRadius: BorderRadius.circular(16), 18 | ), 19 | textStyle: AppTextStyle.title1.copyWith( 20 | color: AppColor().brand3, 21 | ), 22 | ), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/util/app_format_handler.dart: -------------------------------------------------------------------------------- 1 | abstract final class AppFormatHandler { 2 | /// 3 | /// 스킬 리스트 json을 변화할 떄 사용하는메소드 4 | /// 5 | static Map>> parseMapSLMaSSJson( 6 | Map jsonData) { 7 | return jsonData.map((key, value) { 8 | return MapEntry( 9 | key, 10 | List>.from( 11 | value.map((item) => Map.from(item)), 12 | ), 13 | ); 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/util/app_logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | 3 | final logger = Logger(); 4 | -------------------------------------------------------------------------------- /lib/core/constants/content_filter_category_type.enum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 콘텐츠(yotubue) 카테고리 타입 3 | /// 4 | enum ContentFilterCategoryType { 5 | all(''), 6 | jobGroup('related_job_group_ids'), 7 | skill('related_skill_ids'); 8 | 9 | /// firestore document 필드명 10 | final String documentFieldName; 11 | 12 | const ContentFilterCategoryType(this.documentFieldName); 13 | 14 | bool get isAll => this == ContentFilterCategoryType.all; 15 | bool get isJobGroup => this == ContentFilterCategoryType.jobGroup; 16 | bool get isSkill => this == ContentFilterCategoryType.skill; 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/constants/interview_greetings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:easy_localization/easy_localization.dart'; 4 | import 'package:techtalk/app/localization/locale_keys.g.dart'; 5 | 6 | const _greetings = [ 7 | '좋은 결과 있기를 기도하겠습니다!', 8 | '노력한만큼 좋은 결과가 있을거에요!', 9 | '열심히 하는 모습이 보기 좋아요!', 10 | ]; 11 | 12 | String greetingToInterviewee(String nickname) { 13 | return '${tr( 14 | LocaleKeys.undefined_greetingMessage, 15 | namedArgs: { 16 | 'nickname': nickname, 17 | }, 18 | )} ${_greetings[Random().nextInt(2)]}'; 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/constants/slack_notification_type.enum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Slack Notification 타입 3 | /// 4 | enum SlackNotificationType { 5 | signUp(':rocket:'), 6 | logOut(':hanKey:'), 7 | login(':door:'), 8 | event(':fire:'), 9 | withdraw(':cry:'); 10 | 11 | final String icon; 12 | 13 | const SlackNotificationType(this.icon); 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/firebase_pagination_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | class FirebasePaginatedResult { 4 | FirebasePaginatedResult({ 5 | required this.items, 6 | this.lastDocument, 7 | this.lastDocumentId, 8 | required this.hasMore, 9 | this.hasReversedQueryCallProceeded = false, 10 | }); 11 | 12 | final List items; 13 | final DocumentSnapshot? lastDocument; 14 | final String? lastDocumentId; 15 | final bool hasMore; 16 | final bool hasReversedQueryCallProceeded; // isLessThan 여부를 반환 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/helper/bool_extension.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// boolean 비교 연산자를 간편하고 3 | /// 직관적으로 관리할 수 있도록 도와주는 extension 4 | /// 5 | 6 | extension BoolExtension on bool { 7 | bool get isTrue => this == true; 8 | 9 | bool get isFalse => this == false; 10 | } 11 | -------------------------------------------------------------------------------- /lib/core/helper/cached_image_size_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /** Created By Ximya - 2023.08.08 4 | * 이미지의 캐시 크기를 계산하는 확장(extension). 이 확장은 주어진 double,int 값에 5 | * 기반하여 이미지의 크기를 캐시 크기로 변환함. 6 | * 디바이스의 픽셀 비율을 고려하여 다양한 해상도에서 일관된 화질을 유지합니다. 7 | * 8 | * 사용 방법: 9 | * double imageWidth = 100.0; 10 | * int cachedWidth = imageWidth.cacheSize(context); 11 | * 12 | * int imageHeight = 200; 13 | * int cachedHeight = imageHeight.cacheSize(context); 14 | * 15 | * context 캐시 크기를 계산하는 데 사용되는 BuildContext. 16 | * 주어진 double,int 값에 디바이스의 픽셀 비율을 곱하고, 가장 가까운 정수로 반올림한 값을 반환합니다. 17 | * 18 | * 19 | * 레퍼런스 : https://github.com/flutter/flutter/issues/56239 20 | */ 21 | 22 | extension CachedImgSizeExtension on num { 23 | int cacheSize(BuildContext context) { 24 | return (this * MediaQuery.of(context).devicePixelRatio).ceil(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/core/helper/debouncer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class Debouncer { 4 | Duration delay; 5 | Timer? _timer; 6 | 7 | Debouncer( 8 | this.delay, 9 | ); 10 | 11 | run(void Function() callback) { 12 | _timer?.cancel(); 13 | _timer = Timer(delay, callback); 14 | } 15 | 16 | dispose() { 17 | _timer?.cancel(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/helper/duration_extension.dart: -------------------------------------------------------------------------------- 1 | extension DurationExtension on Duration { 2 | // 타임스탬프 형식 변환 함수 3 | String get formatTimestamp { 4 | final duration = Duration(seconds: inSeconds); 5 | String twoDigits(int n) => n.toString().padLeft(2, '0'); 6 | final hours = duration.inHours; 7 | final minutes = duration.inMinutes.remainder(60); 8 | final seconds = duration.inSeconds.remainder(60); 9 | 10 | if (hours > 0) { 11 | return '${twoDigits(hours)}:${twoDigits(minutes)}:${twoDigits(seconds)}'; 12 | } else { 13 | return '${twoDigits(minutes)}:${twoDigits(seconds)}'; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/helper/global_event_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 4 | /// 위젯에 할당된 [GlobalKey]값을 기반으로 5 | /// 해당 위젯의 '높이' '넓이' 'x,y position' 값에 접근할 수 있도록 6 | /// 도와주는 extension 7 | /// 8 | extension GlobalKeyExtension on GlobalKey { 9 | RenderBox get renderBox { 10 | return currentContext?.findRenderObject() as RenderBox; 11 | } 12 | 13 | Offset get globalPosition { 14 | return renderBox.localToGlobal(Offset.zero); 15 | } 16 | 17 | double get height { 18 | return renderBox.size.height; 19 | } 20 | 21 | double get width { 22 | return renderBox.size.width; 23 | } 24 | 25 | double get left { 26 | return globalPosition.dx; 27 | } 28 | 29 | double get top { 30 | return globalPosition.dy; 31 | } 32 | 33 | double get distance { 34 | return globalPosition.distance; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/core/helper/hook_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_hooks/flutter_hooks.dart'; 3 | 4 | /// 5 | /// 'useEffect'와 'addPostFrameCallback' 메소드가 6 | /// 적용되어 있는 effect 메소드 7 | /// 8 | void usePostFrameEffect(Function callback, [List? keys]) { 9 | useEffect( 10 | () { 11 | WidgetsBinding.instance.addPostFrameCallback((_) { 12 | callback(); 13 | }); 14 | 15 | return null; 16 | }, 17 | keys, 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/helper/string_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | abstract class StringGenerator { 4 | StringGenerator._(); 5 | 6 | static String generateRandomString() { 7 | const String characters = 8 | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 9 | 10 | Random random = Random(); 11 | 12 | StringBuffer buffer = StringBuffer(); 13 | for (int i = 0; i < 20; i++) { 14 | int randomIndex = random.nextInt(characters.length); 15 | buffer.write(characters[randomIndex]); 16 | } 17 | 18 | return buffer.toString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/core/helper/validation_extension.dart: -------------------------------------------------------------------------------- 1 | extension StringValidationExt on String { 2 | // 공백 존재 여부 3 | bool get hasSpace { 4 | return RegExp(r'\s').hasMatch(this); 5 | } 6 | 7 | bool get hasProperLength { 8 | return trim().length < 2 || trim().length > 10; 9 | } 10 | 11 | /// 적합한 문자 사용 여부 12 | /// 한글, 알파벳, 숫자, 언더스코어(_), 하이픈(-)만 사용할 수 있음 13 | bool get hasProperCharacter { 14 | return !RegExp(r'^[a-zA-Z0-9ㄱ-ㅎ가-힣_-]+$').hasMatch(trim()); 15 | } 16 | 17 | // Operation 글자 포함 여부 18 | bool get hasContainOperationWord { 19 | return RegExp('운영자|관리자|테크톡|TechTalk|techtalk').hasMatch(trim()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/core/modules/base_use_case/base_no_future_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | abstract class BaseNoFutureUseCase { 4 | RESPONSE call(REQUEST param); 5 | 6 | BaseNoFutureUseCase() { 7 | onInit(); 8 | } 9 | 10 | @protected 11 | void onInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/modules/base_use_case/base_no_networking_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | abstract class BaseNoNetworkingUseCase { 4 | RESPONSE call(REQUEST param); 5 | 6 | BaseNoNetworkingUseCase() { 7 | onInit(); 8 | } 9 | 10 | @protected 11 | void onInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/modules/base_use_case/base_no_param_stream_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | abstract class BaseNoParamStreamUseCase { 4 | Stream call(); 5 | 6 | BaseNoParamStreamUseCase() { 7 | onInit(); 8 | } 9 | 10 | @protected 11 | void onInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/modules/base_use_case/base_no_param_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | abstract class BaseNoParamUseCase { 6 | FutureOr call(); 7 | 8 | BaseNoParamUseCase() { 9 | onInit(); 10 | } 11 | 12 | @protected 13 | void onInit() {} 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/modules/base_use_case/base_stream_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | abstract class BaseStreamUseCase { 4 | Stream call(REQUEST request); 5 | 6 | BaseStreamUseCase() { 7 | onInit(); 8 | } 9 | 10 | @protected 11 | void onInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/modules/base_use_case/base_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | abstract class BaseUseCase { 6 | FutureOr call(REQUEST request); 7 | 8 | BaseUseCase() { 9 | onInit(); 10 | } 11 | 12 | @protected 13 | void onInit() {} 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/modules/converter/duration_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | class DurationConverter implements JsonConverter { 4 | const DurationConverter(); 5 | 6 | @override 7 | Duration? fromJson(int? json) { 8 | if (json == null) return Duration.zero; 9 | return Duration(seconds: json); 10 | } 11 | 12 | @override 13 | int? toJson(Duration? object) => object?.inSeconds; 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/modules/converter/time_stamp_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | /// 5 | /// [freezed] 패키지에서 [Timestamp] 타입을 지원하지 않기 때문에 6 | /// [Timestamp] 데이터를 [DateTime]으로 변환하도록 도와주눈 converter 7 | /// 8 | class TimeStampConverter implements JsonConverter { 9 | const TimeStampConverter(); 10 | 11 | @override 12 | DateTime fromJson(Timestamp timestamp) => timestamp.toDate(); 13 | 14 | @override 15 | Timestamp toJson(DateTime date) => Timestamp.fromDate(date); 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/modules/device/app_device.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:device_info_plus/device_info_plus.dart'; 4 | 5 | abstract final class AppDevice { 6 | static late final AndroidDeviceInfo android; 7 | static late final IosDeviceInfo ios; 8 | 9 | static Future init() async { 10 | final plugin = DeviceInfoPlugin(); 11 | 12 | if (Platform.isIOS) { 13 | ios = await plugin.iosInfo; 14 | } else { 15 | android = await plugin.androidInfo; 16 | } 17 | } 18 | 19 | static bool get isIpad { 20 | if (Platform.isIOS) return false; 21 | 22 | return ios.model.toLowerCase().contains('ipad'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/modules/error_handling/result.dart: -------------------------------------------------------------------------------- 1 | abstract class Result { 2 | static Result failure(Exception error) => Failure(error); 3 | 4 | static Result success(T value) => Success(value); 5 | 6 | T getOrThrow() { 7 | return this is Success 8 | ? (this as Success).value 9 | : throw (this as Failure).error; 10 | } 11 | 12 | R fold({ 13 | required R Function(T value) onSuccess, 14 | required R Function(Exception e) onFailure, 15 | }) { 16 | return this is Success 17 | ? onSuccess((this as Success).value) 18 | : onFailure((this as Failure).error); 19 | } 20 | } 21 | 22 | class Success extends Result { 23 | final T value; 24 | 25 | Success(this.value); 26 | } 27 | 28 | class Failure extends Result { 29 | final Exception error; 30 | 31 | Failure(this.error); 32 | } 33 | -------------------------------------------------------------------------------- /lib/core/modules/exceptions/network_exception.dart: -------------------------------------------------------------------------------- 1 | class NetworkException implements Exception { 2 | NetworkException({ 3 | required this.code, 4 | required this.message, 5 | }); 6 | 7 | final String code; 8 | final String message; 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/query_constraints_applier.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:techtalk/core/firebase_query_constraints.dart'; 3 | 4 | class QueryConstraintApplier { 5 | // 싱글톤 인스턴스를 저장할 정적 변수 6 | static final QueryConstraintApplier _instance = QueryConstraintApplier._internal(); 7 | 8 | // 외부에서 인스턴스에 접근할 수 있는 팩토리 생성자 9 | factory QueryConstraintApplier() { 10 | return _instance; 11 | } 12 | 13 | // 내부 생성자 14 | QueryConstraintApplier._internal(); 15 | 16 | /// 주어진 [query]에 [constraints]를 순차적으로 적용하고 수정된 쿼리를 반환합니다. 17 | Query applyConstraints( 18 | Query query, 19 | List constraints, 20 | ) { 21 | for (var constraint in constraints) { 22 | query = constraint.apply(query); 23 | } 24 | return query; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/core/services/dialog_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:techtalk/app/router/router.dart'; 3 | 4 | class DialogService { 5 | DialogService._(); 6 | 7 | static void show({ 8 | required Dialog dialog, 9 | bool? dismissible, 10 | }) { 11 | showDialog( 12 | barrierDismissible: dismissible ?? true, 13 | context: rootNavigatorKey.currentContext!, 14 | builder: (_) => dialog, 15 | ); 16 | } 17 | 18 | static Future asyncShow({ 19 | required Dialog dialog, 20 | bool? dismissible, 21 | }) { 22 | return Future.value( 23 | showDialog( 24 | barrierDismissible: dismissible ?? true, 25 | context: rootNavigatorKey.currentContext!, 26 | builder: (_) => dialog, 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/features/auth/data_source/remote/auth_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | 3 | abstract interface class AuthRemoteDataSource { 4 | /// 5 | /// 구글 Auth trigger 6 | /// 7 | Future signInWithGoogle(); 8 | 9 | /// 10 | /// 애플 Auth Trigger. 11 | /// 12 | Future signInWithApple(); 13 | 14 | /// 15 | /// Auth 로그아웃 16 | /// 17 | Future signOut(); 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/auth/repositories/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:techtalk/core/index.dart'; 3 | 4 | import 'entities/user_account_provider.enum.dart'; 5 | 6 | abstract interface class AuthRepository { 7 | /// 8 | /// OAuth [provider]를 통해 로그인 9 | /// 10 | Future> signInOAuth(UserAccountProvider provider); 11 | 12 | /// 13 | /// Ouath 로그아웃 14 | /// 15 | Future> signOutOauth(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/auth/repositories/entities/user_account_provider.enum.dart: -------------------------------------------------------------------------------- 1 | enum UserAccountProvider { 2 | google, 3 | apple; 4 | } 5 | -------------------------------------------------------------------------------- /lib/features/auth/usecases/sign_in_oauth_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:techtalk/core/index.dart'; 5 | import 'package:techtalk/features/auth/auth.dart'; 6 | 7 | final class SignInOAuthUseCase 8 | extends BaseUseCase> { 9 | SignInOAuthUseCase(this._authRepository); 10 | 11 | final AuthRepository _authRepository; 12 | 13 | @override 14 | FutureOr> call(UserAccountProvider request) async => 15 | _authRepository.signInOAuth(request); 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/auth/usecases/sign_out_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/modules/base_use_case/base_no_param_use_case.dart'; 4 | import 'package:techtalk/core/modules/error_handling/result.dart'; 5 | import 'package:techtalk/features/auth/auth.dart'; 6 | 7 | final class SignOutUseCase extends BaseNoParamUseCase> { 8 | SignOutUseCase(this._authRepository); 9 | 10 | final AuthRepository _authRepository; 11 | 12 | @override 13 | FutureOr> call() async => _authRepository.signOutOauth(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/blog/data_sources/remote/blog_ref.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:techtalk/features/blog/repository/entity/blog_shell_entity.dart'; 3 | 4 | class FirestoreBlogRef { 5 | static const String _collection = 'Blogs'; 6 | 7 | static CollectionReference> _ref = 8 | FirebaseFirestore.instance.collection(_collection); 9 | 10 | static CollectionReference collection() { 11 | return _ref.withConverter( 12 | fromFirestore: BlogShellEntity.fromFirestore, 13 | toFirestore: (value, options) => value.toFirestore(), 14 | ); 15 | } 16 | 17 | static DocumentReference doc(String id) { 18 | return collection().doc(id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/blog/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/di/app_binding.dart'; 2 | 3 | import 'package:techtalk/features/blog/data_sources/remote/blog_remote_data_source.dart'; 4 | 5 | import 'package:techtalk/features/blog/repository/blog_repository.dart'; 6 | 7 | import 'package:techtalk/features/blog/use_case/get_blog_contents_use_case.dart'; 8 | 9 | export 'data_sources/remote/blog_remote_data_source.dart'; 10 | export 'repository/blog_repository.dart'; 11 | export 'repository/blog_repository_impl.dart'; 12 | export 'repository/entity/blog_shell_entity.dart'; 13 | export 'use_case/get_blog_contents_use_case.dart'; 14 | 15 | // Data Sources 16 | final blogRemoteDataSource = locator(); 17 | 18 | // Repositories 19 | final blogRepository = locator(); 20 | 21 | // Use Cases 22 | final getBlogOverviewListUseCase = locator(); 23 | -------------------------------------------------------------------------------- /lib/features/blog/repository/enum/blog_platform_type.enum.dart: -------------------------------------------------------------------------------- 1 | enum BlogPlatformType { 2 | owned, // 자체 블로그 3 | medium, // 미디엄 4 | tistory, // 티스토리 5 | velog, // 벨로그 6 | undefined; 7 | 8 | static BlogPlatformType getById(String id) => values.firstWhere( 9 | (type) => type.name == id, 10 | orElse: () => BlogPlatformType.undefined, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/blog/use_case/param/get_blog_contents_params.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:techtalk/core/firebase_query_constraints.dart'; 3 | 4 | class GetBlogContentsParams { 5 | final DocumentSnapshot? lastDocument; 6 | final int limit; 7 | final String orderByField; 8 | final List? queryConstraints; 9 | final bool isHalfOfRandomCalled; 10 | final double random; 11 | final String randomKey; 12 | 13 | const GetBlogContentsParams({ 14 | this.lastDocument, 15 | required this.limit, 16 | required this.orderByField, 17 | this.queryConstraints, 18 | required this.isHalfOfRandomCalled, 19 | required this.random, 20 | required this.randomKey, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/entities/base_qna_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/chat/repositories/enums/qna_type.enum.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | 4 | abstract class BaseQnaEntity { 5 | /// 고유 id 값 6 | final String id; 7 | 8 | /// 질문 9 | final String question; 10 | 11 | /// 문답 유형 12 | final QnaType type; 13 | 14 | BaseQnaEntity({ 15 | String? id, 16 | required this.question, 17 | required this.type, 18 | }) : id = id ?? const Uuid().v1(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/entities/chat_history_collection_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/chat/chat.dart'; 2 | 3 | class ChatHistoryCollectionEntity { 4 | final List chatHistories; 5 | final List progressQnaIds; 6 | 7 | ChatHistoryCollectionEntity( 8 | {required this.chatHistories, required this.progressQnaIds}); 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/entities/youtube_interview_room_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/youtube/repositories/entities/video_overview_entity.dart'; 2 | 3 | final class YoutubeInterviewRoomEntity { 4 | final String contentTitle; 5 | final String contentId; 6 | final VideoOverviewEntity? relatedVideo; 7 | 8 | const YoutubeInterviewRoomEntity({ 9 | required this.contentId, 10 | required this.contentTitle, 11 | required this.relatedVideo, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/enums/ai_answer_progress.enum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 유저의 답변에 대한 3 | /// 면접관(GPT) 피드백 상태 4 | /// 5 | enum AiAnswerProgress { 6 | init, // 초기 상태 7 | onProgress, // 확인 중 8 | completed; // 확인 완료 9 | 10 | bool get isOnProgress => this == AiAnswerProgress.onProgress; 11 | bool get isCompleted => this == AiAnswerProgress.completed; 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/enums/chat_type.enum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 채팅 유형 3 | /// 4 | enum ChatType { 5 | reply, // 유저 응답 6 | guide, // 일반 가이드 텍스트 7 | feedback, // 유저의 면답 답변을 대합 대답 8 | question; // 유저에게 물어보는 면접 질문 9 | 10 | bool get isSentMessage => this == ChatType.reply; 11 | bool get isReceivedMessage => this != ChatType.reply; 12 | bool get isQuestionMessage => this == ChatType.question; 13 | bool get isFeedbackMessage => this == ChatType.feedback; 14 | bool get isGuideMessage => this == ChatType.guide; 15 | 16 | static ChatType getTypeById(String id) { 17 | return values.firstWhere( 18 | (type) => type.name == id, 19 | orElse: () => throw Exception('Unexpected Question Id Value'), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/enums/follow_up_status.enum.dart: -------------------------------------------------------------------------------- 1 | enum FollowupStatus { 2 | yes, 3 | no; 4 | 5 | bool get isFollowup => this == FollowupStatus.yes; 6 | } 7 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/enums/interview_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/constants/assets.dart'; 2 | 3 | enum InterviewResult { 4 | pass(Assets.iconsPassResult), 5 | failed(Assets.iconsFailResult); 6 | 7 | final String illustration; 8 | 9 | const InterviewResult(this.illustration); 10 | 11 | bool get isPassed => this == InterviewResult.pass; 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/chat/repositories/enums/resume_question_type.enum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 이력서(+포트폴리오) 질문 타입 3 | /// 4 | enum ResumeQuestionType { 5 | hardSkill('하드 스킬'), 6 | softSkill('소프트 스킬'); 7 | 8 | final String label; 9 | 10 | const ResumeQuestionType(this.label); 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/chat/use_cases/create_chat_messages_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/modules/error_handling/result.dart'; 2 | import 'package:techtalk/features/chat/chat.dart'; 3 | 4 | /// 5 | /// 채팅 메세지 데이터 생성 6 | /// 7 | 8 | final class CreateChatMessagesUseCase { 9 | CreateChatMessagesUseCase(this._repository); 10 | 11 | final ChatRepository _repository; 12 | 13 | Future> call({ 14 | required String chatRoomId, 15 | required List messages, 16 | }) { 17 | return _repository.uploadChats( 18 | chatRoomId, 19 | messages: messages, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/features/chat/use_cases/create_chat_room_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/modules/error_handling/result.dart'; 2 | import 'package:techtalk/features/chat/chat.dart'; 3 | 4 | final class CreateChatRoomUseCase { 5 | CreateChatRoomUseCase(this._chatRepository); 6 | final ChatRepository _chatRepository; 7 | 8 | Future> call({ 9 | required ChatRoomEntity room, 10 | required List qnas, 11 | required List messages, 12 | }) async { 13 | return _chatRepository.createChatRoom( 14 | room: room, 15 | qnas: qnas, 16 | messages: messages, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/features/chat/use_cases/get_chat_message_history_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/index.dart'; 2 | import 'package:techtalk/features/chat/chat.dart'; 3 | 4 | class GetChatMessageHistoryUseCase 5 | extends BaseUseCase> { 6 | GetChatMessageHistoryUseCase(this._repository); 7 | 8 | final ChatRepository _repository; 9 | 10 | @override 11 | Future> call(String roomId) async { 12 | return _repository.getChatHistory(roomId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/chat/use_cases/get_chat_qnas_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/util/app_logger.dart'; 2 | import 'package:techtalk/core/modules/error_handling/result.dart'; 3 | import 'package:techtalk/features/chat/chat.dart'; 4 | 5 | final class GetChatQnasUseCase { 6 | GetChatQnasUseCase(this._chatRepository); 7 | 8 | final ChatRepository _chatRepository; 9 | 10 | Future>> call(ChatRoomEntity room) async { 11 | return room.type.typedBranch( 12 | common: (_) => _chatRepository.getCommonChatQnas(room), 13 | resume: (_) => _chatRepository.getResumeChatQnas(room), 14 | youtube: (_) { 15 | logger.e('유튜브 면접을 채팅 기록을 반환하지 않음'); 16 | return Result.success([]); 17 | }, 18 | proficiency: (_) { 19 | logger.e('역량별 면접을 채팅 기록을 반환하지 않음'); 20 | return Result.success([]); 21 | }, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/features/chat/use_cases/get_chat_rooms_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/modules/error_handling/result.dart'; 2 | import 'package:techtalk/features/chat/chat.dart'; 3 | import 'package:techtalk/features/topic/topic.dart'; 4 | 5 | class GetChatRoomsUseCase { 6 | GetChatRoomsUseCase(this._repository); 7 | 8 | final ChatRepository _repository; 9 | 10 | Future>> call( 11 | InterviewType type, [ 12 | TopicEntity? topic, 13 | ]) { 14 | return _repository.getChatRooms(type, topic); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/chat/use_cases/report_chat_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/modules/error_handling/result.dart'; 2 | import 'package:techtalk/features/chat/chat.dart'; 3 | 4 | class ReportChatUseCase { 5 | ReportChatUseCase(this._repository); 6 | 7 | final ChatRepository _repository; 8 | 9 | Future> call( 10 | FeedbackChatEntity feedback, 11 | AnswerChatEntity answer, 12 | ) async { 13 | return _repository.uploadChatIssueReport(feedback, answer); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/interview/data_source/local/interview_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 2 | 3 | abstract class InterviewLocalDataSource { 4 | /// 직군 관련 면접 질문 리스트 호출 5 | List>> getJobGroupQuestionHistory(); 6 | 7 | /// 스킬 관련 면접 질문 리스트 호출 8 | List>> getSkillQuestionHistory(); 9 | 10 | Future storeTechSetQuestionHistory( 11 | List>> history); 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/interview/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/di/index.dart'; 2 | import 'package:techtalk/features/interview/data_source/local/interview_local_data_source.dart'; 3 | import 'package:techtalk/features/interview/repository/interview_repository.dart'; 4 | 5 | final interviewLocalDataSource = locator(); 6 | final interviewRepository = locator(); 7 | -------------------------------------------------------------------------------- /lib/features/interview/repository/interview_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/modules/error_handling/result.dart'; 2 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 3 | 4 | abstract class InterviewRepository { 5 | /// 직군별 질문 히스토리를 가져옵니다. 6 | Result>>> getJobGroupQuestionHistory(); 7 | 8 | /// 스킬별 질문 히스토리를 가져옵니다. 9 | Result>>> getSkillQuestionHistory(); 10 | 11 | /// TechSet별 질문 히스토리를 저장합니다. 12 | Future> storeTechSetQuestionHistory( 13 | List>> history); 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/system/data_source/local/boxes/system_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive_flutter/hive_flutter.dart'; 2 | 3 | part 'system_box.g.dart'; 4 | 5 | /// 6 | /// 앱의 전박적인 시스템 설정(locale, version, 마지막 접속일 등등)과 관련된 7 | /// 로컬 스토리지 데이터 8 | /// 추가 항목이 필요할 시 아래 멤버 변수에 추가하여 관리 9 | /// 10 | @HiveType(typeId: 3) 11 | class SystemBox extends HiveObject { 12 | /// 유저의 locale 13 | @HiveField(0) 14 | final String languageCode; 15 | 16 | SystemBox({required this.languageCode}); 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/system/data_source/local/system_local_data_source.dart: -------------------------------------------------------------------------------- 1 | abstract interface class SystemLocalDataSource { 2 | /// 3 | /// 마지막으로 설정된 locale language code 정보 호출 4 | /// 5 | Future loadLocaleLanguageCode(); 6 | 7 | /// 8 | /// 현재 유저의 locale 정보 호출 9 | /// 10 | Future storeLocaleCode(String code); 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/system/data_source/remote/system_ref.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:techtalk/features/system/system.dart'; 3 | 4 | abstract class FirestoreVersionRef { 5 | static const String name = 'Version'; 6 | 7 | static CollectionReference collection() => 8 | FirebaseFirestore.instance.collection(name).withConverter( 9 | fromFirestore: VersionModel.fromFirestore, 10 | toFirestore: (value, options) => value.toJson(), 11 | ); 12 | 13 | static DocumentReference doc(String id) => 14 | FirebaseFirestore.instance.collection(name).doc(id).withConverter( 15 | fromFirestore: VersionModel.fromFirestore, 16 | toFirestore: (value, options) => value.toJson(), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/system/data_source/remote/system_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/system/system.dart'; 2 | 3 | abstract interface class SystemRemoteDataSource { 4 | /// 5 | /// 앱 버전 정보 호출 6 | /// 7 | Future getVersionInfo(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/features/system/data_source/remote/system_remote_data_source_impl.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:techtalk/features/system/system.dart'; 6 | 7 | final class SystemRemoteDataSourceImpl implements SystemRemoteDataSource { 8 | @override 9 | Future getVersionInfo() async { 10 | try { 11 | final targetDocPath = Platform.isIOS ? 'ios' : 'android'; 12 | 13 | final versionRef = await FirestoreVersionRef.doc(targetDocPath) 14 | .get(const GetOptions(source: Source.server)); 15 | 16 | return versionRef.data()!; 17 | } catch (e) { 18 | log('버전 정보 호출 실패 : $e'); 19 | rethrow; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/features/system/repositories/entities/youtube_gpt_model_type.enum.dart: -------------------------------------------------------------------------------- 1 | enum YoutubeGptModelType { 2 | gpt4o('gpt-4o'), 3 | o1('o1'), 4 | o3mini('o3-mini'); 5 | 6 | final String id; 7 | 8 | const YoutubeGptModelType(this.id); 9 | 10 | static YoutubeGptModelType getById(String id) { 11 | return values.firstWhere( 12 | (topic) => topic.id == id, 13 | orElse: () => YoutubeGptModelType.gpt4o, 14 | ); 15 | } 16 | 17 | bool get isGpt4o => this == YoutubeGptModelType.gpt4o; 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/system/repositories/system_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:techtalk/core/modules/error_handling/result.dart'; 4 | import 'package:techtalk/features/system/system.dart'; 5 | 6 | abstract interface class SystemRepository { 7 | /// 8 | /// 앱 버전 정보 호출 9 | /// 10 | Future> getVersionInfo(); 11 | 12 | /// 13 | /// 마지막으로 설저된 local 정보 호출 14 | /// 15 | Future> getStoredLocale(); 16 | 17 | /// 18 | /// [Locale] language코드를 로컬 스토리지에 저장 19 | /// 20 | Future> storeLocale(Locale currentLocale); 21 | } 22 | -------------------------------------------------------------------------------- /lib/features/system/use_cases/get_version_info_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/index.dart'; 2 | import 'package:techtalk/features/system/system.dart'; 3 | 4 | class GetVersionInfoUseCase extends BaseNoParamUseCase> { 5 | GetVersionInfoUseCase(this._repository); 6 | 7 | final SystemRepository _repository; 8 | 9 | @override 10 | Future> call() => _repository.getVersionInfo(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/tech_set/data_source/local/boxes/tech_set_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'tech_set_box.g.dart'; 4 | 5 | @HiveType(typeId: 4) 6 | class TechSetBox extends HiveObject { 7 | /// 테크 스킬 8 | @HiveField(0) 9 | final Map>>>? skillJson; 10 | 11 | TechSetBox({required this.skillJson}); 12 | 13 | TechSetBox copyWith({ 14 | Map>>>? skillJson, 15 | }) { 16 | return TechSetBox( 17 | skillJson: skillJson ?? this.skillJson, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/tech_set/data_source/local/tech_set_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 2 | 3 | abstract interface class TechSetLocalDataSource { 4 | /// 개발 직군 리스트 호출 5 | Future> getJobs(); 6 | 7 | /// 캐싱된 스킬 리스트 호출 8 | Map>>>? loadCachedSkillSet(); 9 | 10 | /// 스킬 리스트 > 로컬에 저장 11 | Future storeSkillSet( 12 | {required Map>>> skillSet}); 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/tech_set/data_source/remote/model/tech_set_keys_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'tech_set_keys_model.g.dart'; 4 | 5 | @JsonSerializable() 6 | class TechSetKeysModel { 7 | final String? skill; 8 | 9 | TechSetKeysModel({required this.skill}); 10 | 11 | factory TechSetKeysModel.fromJson(Map json) => 12 | _$TechSetKeysModelFromJson(json); 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/tech_set/data_source/remote/model/tech_set_keys_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'tech_set_keys_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | TechSetKeysModel _$TechSetKeysModelFromJson(Map json) => 10 | TechSetKeysModel( 11 | skill: json['skill'] as String?, 12 | ); 13 | 14 | Map _$TechSetKeysModelToJson(TechSetKeysModel instance) => 15 | { 16 | 'skill': instance.skill, 17 | }; 18 | -------------------------------------------------------------------------------- /lib/features/tech_set/data_source/remote/skill_ref.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:techtalk/features/tech_set/data_source/remote/model/skill_model.dart'; 3 | 4 | abstract class FirestoreSkillRef { 5 | static const String _name = 'Skill'; 6 | 7 | static CollectionReference collection() => 8 | FirebaseFirestore.instance.collection(_name).withConverter( 9 | fromFirestore: SkillModel.fromFirestore, 10 | toFirestore: (value, options) => value.toJson(), 11 | ); 12 | 13 | static DocumentReference document(String channelId) => 14 | FirebaseFirestore.instance.collection(_name).doc(channelId).withConverter( 15 | fromFirestore: SkillModel.fromFirestore, 16 | toFirestore: (value, options) => value.toJson(), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/tech_set/data_source/remote/tech_set_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/tech_set/data_source/remote/model/job_group_model.dart'; 2 | import 'package:techtalk/features/tech_set/data_source/remote/model/skill_model.dart'; 3 | 4 | abstract class TechSetRemoteDataSource { 5 | /// 스킬 항목 리스트 호출 6 | Future> getSkills(); 7 | 8 | /// 직군 리스트 호출 9 | Future> getJobGroups(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/features/tech_set/repositories/enums/skill_category.enum.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:get_it/get_it.dart'; 3 | 4 | enum SkillCategory { 5 | data, 6 | mobile, 7 | frontend, 8 | backend, 9 | testing, 10 | database, 11 | language, 12 | cs, 13 | pattern, 14 | none; 15 | 16 | static SkillCategory fromKey(String key) { 17 | final target = SkillCategory.values.firstWhereOrNull((e) => e.name == key); 18 | return target ?? SkillCategory.none; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/tech_set/repositories/enums/tech_set_type.enum.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | import 'package:techtalk/app/localization/locale_keys.g.dart'; 3 | 4 | enum TechSetType { 5 | jobGroup(LocaleKeys.techSet_type_jobGroup), 6 | skill(LocaleKeys.techSet_type_skill); 7 | 8 | final String labelKey; 9 | 10 | const TechSetType(this.labelKey); 11 | 12 | String get label => tr(labelKey); 13 | 14 | bool get isSkill => TechSetType.skill == this; 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/tech_set/repositories/tech_set_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/features/chat/repositories/entities/selectable_qna_entity.dart'; 4 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 5 | import 'package:techtalk/features/topic/repositories/entities/common_qna_entity.dart'; 6 | 7 | abstract interface class TechSetRepository { 8 | /// 개발 직군 리스트 호출 9 | List getJobs(); 10 | 11 | /// 스킬 리스트 초기화 12 | Future initSkills(); 13 | 14 | /// 스킬 리스트 초기화 15 | Future initJobGroups(); 16 | 17 | /// id 값을 기반으로 [SkillEntity]을 리턴 18 | SkillEntity getSkillById(String id); 19 | 20 | /// id 값을 기반으로 [SkillEntity]을 리턴 21 | JobGroupEntity getJobGroupById(String id); 22 | 23 | /// 스킬 리스트 호출 24 | List getSkills(); 25 | } 26 | -------------------------------------------------------------------------------- /lib/features/tech_set/usecases/get_jobs_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 2 | import 'package:techtalk/features/tech_set/tech_set.dart'; 3 | 4 | final class GetJobsUseCase { 5 | GetJobsUseCase( 6 | this._jobRepository, 7 | ); 8 | 9 | final TechSetRepository _jobRepository; 10 | 11 | List call() { 12 | return _jobRepository.getJobs(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/topic/data_source/local/topic_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/topic/topic.dart'; 2 | 3 | abstract interface class TopicLocalDataSource { 4 | /// 5 | /// 문답 목록 호출 6 | /// 7 | QnaListBox? loadQnas(String topicId); 8 | 9 | /// 10 | /// 단일 문답 호출 11 | /// 12 | QnaBox? loadSingleQna({required String topicId, required String qnaId}); 13 | 14 | /// 15 | /// 문답 목록 저장 16 | /// 17 | Future storeQnas( 18 | {required String topicId, required List qnas}); 19 | } 20 | -------------------------------------------------------------------------------- /lib/features/topic/data_source/remote/models/topic_category_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'topic_category_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | TopicCategoryModel _$TopicCategoryModelFromJson(Map json) => 10 | TopicCategoryModel( 11 | id: json['id'] as String, 12 | name: json['name'] as String, 13 | ); 14 | 15 | Map _$TopicCategoryModelToJson(TopicCategoryModel instance) => 16 | { 17 | 'id': instance.id, 18 | 'name': instance.name, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/features/topic/data_source/remote/topic_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/topic/topic.dart'; 2 | 3 | abstract interface class TopicRemoteDataSource { 4 | /// 5 | /// 면접 주제 리스트 호출 6 | /// 7 | Future> getTopics(); 8 | 9 | /// 10 | /// 문답 리스트 호출 11 | /// 12 | Future> getQnas(String topicId); 13 | 14 | /// 15 | /// 단일 문답 호출 16 | /// 17 | Future getQna( 18 | String topicId, 19 | String questionId, 20 | ); 21 | 22 | /// 23 | /// 오답 데이터 추가 및 업데이트 24 | /// 25 | Future updateWrongAnswer( 26 | {required WrongAnswerModel wrongAnswer, required String topicId}); 27 | 28 | /// 29 | /// 오답 목록 호출 30 | /// 31 | Future> getWrongAnswers(String topicId); 32 | 33 | /// 34 | /// 유저 오답 목록 제거 35 | /// 36 | Future deleteUserWrongAnswers(); 37 | } 38 | -------------------------------------------------------------------------------- /lib/features/topic/repositories/entities/wrong_answer_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/topic/repositories/entities/common_qna_entity.dart'; 2 | 3 | class WrongAnswerEntity { 4 | final CommonQnaEntity qna; 5 | final DateTime updatedAt; 6 | final String userAnswer; 7 | final int wrongAnswerCount; 8 | 9 | WrongAnswerEntity({ 10 | required this.qna, 11 | required this.updatedAt, 12 | required this.userAnswer, 13 | required this.wrongAnswerCount, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/topic/repositories/topic_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/index.dart'; 2 | import 'package:techtalk/features/chat/chat.dart'; 3 | import 'package:techtalk/features/topic/topic.dart'; 4 | 5 | abstract interface class TopicRepository { 6 | /// 7 | /// 문답 리스트 호출 8 | /// 9 | Future>> getTopicQnas( 10 | String topicId, 11 | ); 12 | 13 | /// 14 | /// 단일 문답 호출 15 | /// 16 | Future> getTopicQna( 17 | String topicId, 18 | String questionId, 19 | ); 20 | 21 | /// 22 | /// 오답 노트 기록 업데이트 23 | /// 24 | Future> updateWrongAnswer(ChatQnaEntity chatQna); 25 | 26 | /// 27 | /// 오답 노트 목록 호출 28 | /// 29 | Future>> getWrongAnswers(String topicId); 30 | 31 | /// 32 | /// 오답 노트 목록 제거 33 | /// 34 | Future> deleteUserWrongAnswers(); 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/topic/usecases/get_cached_skills_use_case.dart: -------------------------------------------------------------------------------- 1 | // import 'dart:async'; 2 | // 3 | // import 'package:techtalk/core/index.dart'; 4 | // import 'package:techtalk/features/tech_set/repositories/entities/skillt_entity.dart'; 5 | // import 'package:techtalk/features/tech_set/tech_set.dart'; 6 | // 7 | // final class GetCachedSkillsUseCase 8 | // extends BaseNoParamUseCase> { 9 | // @override 10 | // Future> call() async { 11 | // 12 | // final getSkillsKey = await techSetRepository.getKeys(); 13 | // final remoteSkillKey = getSkillsKey.getOrThrow()..skillKey; 14 | // 15 | // final getLocalSkills = await techSetRepository.getSkills(); 16 | // 17 | // 18 | // // final needRemoteFetch = skillKey 19 | // 20 | // } 21 | // } 22 | -------------------------------------------------------------------------------- /lib/features/topic/usecases/get_topic_qnas_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/core/index.dart'; 2 | import 'package:techtalk/features/topic/topic.dart'; 3 | 4 | final class GetTopicQnasUseCase { 5 | const GetTopicQnasUseCase( 6 | this._topicRepository, 7 | ); 8 | 9 | final TopicRepository _topicRepository; 10 | 11 | Future>> call(String topicId) async { 12 | return _topicRepository.getTopicQnas(topicId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/topic/usecases/get_wrong_answers_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/topic/topic.dart'; 5 | 6 | final class GetWrongAnswersUseCase 7 | extends BaseUseCase>> { 8 | GetWrongAnswersUseCase(this._repository); 9 | 10 | final TopicRepository _repository; 11 | 12 | @override 13 | FutureOr>> call(String request) => 14 | _repository.getWrongAnswers(request); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/topic/usecases/update_wrong_answer_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/chat/chat.dart'; 5 | import 'package:techtalk/features/topic/topic.dart'; 6 | 7 | final class UpdateWrongAnswerUseCase 8 | extends BaseUseCase> { 9 | UpdateWrongAnswerUseCase(this._repository); 10 | 11 | final TopicRepository _repository; 12 | 13 | @override 14 | FutureOr> call(ChatQnaEntity request) => 15 | _repository.updateWrongAnswer(request); 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/user/data_source/local/user_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/user/user.dart'; 2 | 3 | abstract interface class UserLocalDataSource { 4 | /// 5 | /// 유저의 로컬 정보 업데이트 6 | /// 7 | Future storeUserLocalInfo(UserEntity user); 8 | 9 | /// 10 | /// 면접을 처음 실행했는지 여부 값 업데이트 11 | /// 12 | Future changeFirstEnteredFieldToTrue(); 13 | 14 | /// 15 | /// 유저 로컬 데이터 로드 16 | /// 17 | UserBox loadUserLocalInfo(); 18 | 19 | /// 20 | /// 유저 앱 평가 요청 가능 상태를 비활성화 21 | /// 22 | Future disableReviewAvailableState(); 23 | 24 | /// 25 | /// 유저 로컬에 새로운 값 저장 26 | /// TODO 27 | /// 나머지 로컬 데이터 저장 로직들을 해당 메소드로 통합 필요 28 | /// 29 | Future storeNewLocalState(UserBox userBox); 30 | } 31 | -------------------------------------------------------------------------------- /lib/features/user/data_source/remote/fire_storage_user_ref.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:firebase_storage/firebase_storage.dart'; 3 | import 'package:uuid/uuid.dart'; 4 | 5 | abstract class FireStorageUserRef { 6 | static const String pathName = 'profileImage'; 7 | static String? get _userUid => FirebaseAuth.instance.currentUser?.uid; 8 | 9 | static Reference get profileImgRef => FirebaseStorage.instance 10 | .ref(pathName) 11 | .child(_userUid ?? const Uuid().v1()); 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/user/data_source/remote/models/marked_common_question_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'marked_common_question_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | MarkedCommonQuestionModel _$MarkedCommonQuestionModelFromJson( 10 | Map json) => 11 | MarkedCommonQuestionModel( 12 | ids: (json['ids'] as List).map((e) => e as String).toList(), 13 | ); 14 | 15 | Map _$MarkedCommonQuestionModelToJson( 16 | MarkedCommonQuestionModel instance) => 17 | { 18 | 'ids': instance.ids, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/features/user/data_source/remote/models/uploaded_youtube_content_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'uploaded_youtube_content_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | UploadedYoutubeModel _$UploadedYoutubeModelFromJson( 10 | Map json) => 11 | UploadedYoutubeModel( 12 | id: json['id'] as String, 13 | uploadAt: 14 | const TimeStampConverter().fromJson(json['upload_at'] as Timestamp), 15 | ); 16 | 17 | Map _$UploadedYoutubeModelToJson( 18 | UploadedYoutubeModel instance) => 19 | { 20 | 'id': instance.id, 21 | 'upload_at': const TimeStampConverter().toJson(instance.uploadAt), 22 | }; 23 | -------------------------------------------------------------------------------- /lib/features/user/data_source/remote/models/watched_youtube_content_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'watched_youtube_content_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | WatchedYoutubeModel _$WatchedYoutubeModelFromJson(Map json) => 10 | WatchedYoutubeModel( 11 | id: json['id'] as String, 12 | watchedAt: 13 | const TimeStampConverter().fromJson(json['watched_at'] as Timestamp), 14 | ); 15 | 16 | Map _$WatchedYoutubeModelToJson( 17 | WatchedYoutubeModel instance) => 18 | { 19 | 'id': instance.id, 20 | 'watched_at': const TimeStampConverter().toJson(instance.watchedAt), 21 | }; 22 | -------------------------------------------------------------------------------- /lib/features/user/usecases/check_nickname_duplication.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | final class CheckNicknameDuplication extends BaseUseCase> { 7 | CheckNicknameDuplication(this._repository); 8 | 9 | final UserRepository _repository; 10 | 11 | @override 12 | FutureOr> call(String request) { 13 | return _repository.isNicknameDuplicated(request); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/user/usecases/create_user_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | final class CreateUserUseCase extends BaseUseCase> { 7 | CreateUserUseCase( 8 | this._userRepository, 9 | ); 10 | 11 | final UserRepository _userRepository; 12 | 13 | @override 14 | Future> call(UserEntity data) async { 15 | await _userRepository.storeUserLocalInfo(data); 16 | return _userRepository.createUser(data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/user/usecases/disable_review_available_state_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | class DisableReviewAvailableStateUseCase 7 | extends BaseNoParamUseCase> { 8 | DisableReviewAvailableStateUseCase(this._repository); 9 | 10 | final UserRepository _repository; 11 | 12 | @override 13 | FutureOr> call() async => 14 | _repository.disableReviewAvailableState(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/user/usecases/get_user_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | final class GetUserUseCase extends BaseNoParamUseCase> { 7 | GetUserUseCase( 8 | this._userRepository, 9 | ); 10 | 11 | final UserRepository _userRepository; 12 | 13 | @override 14 | Future> call() async { 15 | return _userRepository.getUser(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/user/usecases/increase_completed_interview_count_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | final class IncreaseCompletedInterviewCountUseCase 7 | extends BaseNoParamUseCase> { 8 | IncreaseCompletedInterviewCountUseCase(this._repository); 9 | 10 | final UserRepository _repository; 11 | 12 | @override 13 | FutureOr> call() async => 14 | _repository.increaseCompletedInterviewCount(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/user/usecases/resign_user_info_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/topic/topic.dart'; 5 | import 'package:techtalk/features/user/user.dart'; 6 | 7 | final class ResignUserInfoUseCase 8 | extends BaseUseCase> { 9 | ResignUserInfoUseCase(this._userRepository, this._topicRepository); 10 | 11 | final UserRepository _userRepository; 12 | final TopicRepository _topicRepository; 13 | 14 | @override 15 | FutureOr> call(UserEntity request) async { 16 | final topicRes = await _topicRepository.deleteUserWrongAnswers(); 17 | topicRes.getOrThrow(); 18 | 19 | return _userRepository.deleteUser(request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/features/user/usecases/sotre_user_local_info_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | final class StoreUserLocalInfoUseCase 7 | extends BaseUseCase> { 8 | StoreUserLocalInfoUseCase(this._repository); 9 | 10 | final UserRepository _repository; 11 | 12 | @override 13 | FutureOr> call(UserEntity request) { 14 | return _repository.storeUserLocalInfo(request); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/user/usecases/update_last_login_date_use_cae.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/repositories/user_repository.dart'; 5 | 6 | class UpdateLastLoginDateUseCase extends BaseNoParamUseCase> { 7 | UpdateLastLoginDateUseCase(this._repository); 8 | 9 | final UserRepository _repository; 10 | 11 | @override 12 | FutureOr> call() async => _repository.updateLastLoginDate(); 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/user/usecases/update_user_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/user/user.dart'; 5 | 6 | final class UpdateUserUseCase extends BaseUseCase> { 7 | UpdateUserUseCase(this._userRepository); 8 | 9 | final UserRepository _userRepository; 10 | 11 | @override 12 | Future> call(UserEntity request) async { 13 | return _userRepository.updateUser(request); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/youtube/data_source/remote/models/channel_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'channel_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ChannelModel _$ChannelModelFromJson(Map json) => ChannelModel( 10 | id: json['id'] as String, 11 | name: json['name'] as String, 12 | logoUrl: json['logo_url'] as String?, 13 | ); 14 | 15 | Map _$ChannelModelToJson(ChannelModel instance) => 16 | { 17 | 'id': instance.id, 18 | 'name': instance.name, 19 | 'logo_url': instance.logoUrl, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/features/youtube/data_source/remote/models/summary_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'summary_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SummaryModel _$SummaryModelFromJson(Map json) => SummaryModel( 10 | mainTheme: json['main_theme'] as String, 11 | summaries: (json['summaries'] as List) 12 | .map((e) => ParagraphModel.fromJson(e as Map)) 13 | .toList(), 14 | ); 15 | 16 | Map _$SummaryModelToJson(SummaryModel instance) => 17 | { 18 | 'main_theme': instance.mainTheme, 19 | 'summaries': instance.summaries.map((e) => e.toJson()).toList(), 20 | }; 21 | -------------------------------------------------------------------------------- /lib/features/youtube/data_source/remote/models/youtube_detail_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'youtube_detail_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | YoutubeDetailModel _$YoutubeDetailModelFromJson(Map json) => 10 | YoutubeDetailModel( 11 | summary: SummaryModel.fromJson(json['summary'] as Map), 12 | uploaderId: json['uploader_id'] as String, 13 | ); 14 | 15 | Map _$YoutubeDetailModelToJson(YoutubeDetailModel instance) => 16 | { 17 | 'summary': instance.summary.toJson(), 18 | 'uploader_id': instance.uploaderId, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/features/youtube/data_source/remote/models/youtube_qna_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'youtube_qna_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | YoutubeQnaModel _$YoutubeQnaModelFromJson(Map json) => 10 | YoutubeQnaModel( 11 | id: json['id'] as String, 12 | question: json['question'] as String, 13 | answer: json['answer'] as String?, 14 | ); 15 | 16 | Map _$YoutubeQnaModelToJson(YoutubeQnaModel instance) => 17 | { 18 | 'id': instance.id, 19 | 'question': instance.question, 20 | 'answer': instance.answer, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/features/youtube/repositories/entities/channel_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/youtube/data_source/remote/models/channel_model.dart'; 2 | 3 | /// 4 | /// 유튜브 채널 정보 5 | /// 6 | class ChannelEntity { 7 | /// 채널 id 8 | final String id; 9 | 10 | /// 채널명 11 | final String name; 12 | 13 | /// 채널의 로고 이미지 14 | final String? logoUrl; 15 | 16 | /// 채널 url 17 | String get url => 'https://www.youtube.com/channel/$id'; 18 | 19 | ChannelEntity({ 20 | required this.id, 21 | required this.name, 22 | this.logoUrl, 23 | }); 24 | 25 | /// 26 | /// 호출에 실패했을 경우 27 | /// 28 | factory ChannelEntity.undefined() => 29 | ChannelEntity(id: 'undefined', name: '알 수 없는 채널'); 30 | 31 | ChannelModel toModel() => ChannelModel( 32 | id: id, 33 | name: name, 34 | logoUrl: logoUrl, 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /lib/features/youtube/repositories/entities/summary_entity.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'summary_entity.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SummaryEntity _$SummaryEntityFromJson(Map json) => 10 | SummaryEntity( 11 | mainTheme: json['main_theme'] as String, 12 | summaries: (json['summaries'] as List) 13 | .map((e) => ParagraphEntity.fromJson(e as Map)) 14 | .toList(), 15 | ); 16 | 17 | Map _$SummaryEntityToJson(SummaryEntity instance) => 18 | { 19 | 'main_theme': instance.mainTheme, 20 | 'summaries': instance.summaries, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/features/youtube/repositories/entities/video_overview_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:youtube_explode_dart/youtube_explode_dart.dart'; 2 | 3 | final class VideoOverviewEntity { 4 | final String id; 5 | final String thumbnailImgUrl; 6 | final String title; 7 | final String channelName; 8 | 9 | VideoOverviewEntity({ 10 | required this.id, 11 | required this.title, 12 | required this.thumbnailImgUrl, 13 | required this.channelName, 14 | }); 15 | 16 | factory VideoOverviewEntity.fromVideoExplore(Video video) => 17 | VideoOverviewEntity( 18 | id: video.id.value, 19 | title: video.title, 20 | thumbnailImgUrl: video.thumbnails.highResUrl, 21 | channelName: video.author, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/features/youtube/repositories/enums/youtube_content_analyzed_type.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 유튜브 분석 prompt 타입을 나타날 떄 사용하는 enum 3 | /// (요약 / 면접 질문 생성 / 스킬,직군 키워드 매칭) 4 | /// 5 | enum YoutubeContentAnalyzedType { 6 | isValid, // 유효한 콘텐츠 7 | notTech, // 개발 콘텐츠가 아님 8 | lackOfContent, // 개발 관련 콘텐츠더라도 요약, 질문 생성을하기에는 부족 9 | undefined; // 정의가 안된 타입 10 | 11 | static YoutubeContentAnalyzedType getById(String id) { 12 | return YoutubeContentAnalyzedType.values.firstWhere( 13 | (e) { 14 | final safeElement = e.name.toLowerCase(); 15 | final safeId = id.toLowerCase(); 16 | 17 | return safeElement == safeId; 18 | }, 19 | orElse: () => YoutubeContentAnalyzedType.undefined, 20 | ); 21 | } 22 | 23 | bool get isValidContent => this == YoutubeContentAnalyzedType.isValid; 24 | 25 | bool get isInvalid => !(this == YoutubeContentAnalyzedType.isValid); 26 | } 27 | -------------------------------------------------------------------------------- /lib/features/youtube/usecases/get_youtube_video_data_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:techtalk/core/index.dart'; 4 | import 'package:techtalk/features/youtube/index.dart'; 5 | 6 | final class GetYoutubeVideoDataUseCase 7 | extends BaseUseCase> { 8 | GetYoutubeVideoDataUseCase(this._repository); 9 | 10 | final YoutubeRepository _repository; 11 | 12 | @override 13 | Future> call(String request) => 14 | _repository.getYoutubeVideoData(request); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/youtube/usecases/qnas.json: -------------------------------------------------------------------------------- 1 | { 2 | "main_summary" : (핵심 주제), 3 | "summaries" : [ 4 | { 5 | "title" : (제목), 6 | "content" : [(요약1), (요약2)], 7 | "offset" : (자막이 시작된 시간), // 0:10:55.839000 8 | } 9 | ] 10 | 11 | } -------------------------------------------------------------------------------- /lib/presentation/pages/blog/blog_main/widgets/scaffold.p.dart: -------------------------------------------------------------------------------- 1 | part of '../blog_main_page.dart'; 2 | 3 | class _Scaffold extends StatelessWidget { 4 | final Widget categorySliderBar; 5 | final Widget contentListView; 6 | 7 | const _Scaffold({ 8 | required this.categorySliderBar, 9 | required this.contentListView, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Column( 15 | children: [ 16 | categorySliderBar, 17 | const Gap(6), 18 | Expanded(child: contentListView), 19 | ], 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/pages/home/internal_home_event.p.dart: -------------------------------------------------------------------------------- 1 | part of 'home_event.dart'; 2 | 3 | 4 | extension InternalHomeEvent on HomeEvent { 5 | /// 6 | /// 알람 활성화 요청 7 | /// 8 | Future requestNotificationPermission(WidgetRef ref) async { 9 | FirebaseMessaging messaging = FirebaseMessaging.instance; 10 | 11 | NotificationSettings settings = await messaging.requestPermission(); 12 | 13 | await ref.read(notificationStatusProvider.future); 14 | 15 | log('유저 알람 퍼미션 상태 : ${settings.authorizationStatus}'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/pages/home/widgets/youtube_content_feature_card.p.dart: -------------------------------------------------------------------------------- 1 | part of '../home_page.dart'; 2 | 3 | /// 4 | /// 유튜브 면접 신규 기능을 노출하고 5 | /// '유튜브 콘텐츠' 탭뷰로 랜딩을 유도하는 카드뷰 6 | /// 7 | class _YoutubeContentFeatureCard extends ConsumerWidget with HomeEvent { 8 | const _YoutubeContentFeatureCard({super.key}); 9 | 10 | static const InterviewType interviewType = InterviewType.youtube; 11 | 12 | @override 13 | Widget build(BuildContext context, WidgetRef ref) { 14 | return InterviewIndicatorCard( 15 | showNewBadge: true, 16 | logoPath: interviewType.logoPath, 17 | title: '유튜브 콘텐츠', 18 | subDescription: '이력서로 만든 예상 질문을 경험해 보세요!', 19 | onCardTapped: () { 20 | onYoutubeFeatureCardTapped(ref); 21 | }, 22 | showPlustBtn: false, 23 | onPlusSuffixedBtnTapped: () {}, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/presentation/pages/home/widgets/youtube_interview_card.p.dart: -------------------------------------------------------------------------------- 1 | part of '../home_page.dart'; 2 | 3 | /// 4 | /// 유튜브 면접 카드 5 | /// 6 | class _YoutubeInterviewCard extends ConsumerWidget with HomeState, HomeEvent { 7 | const _YoutubeInterviewCard({super.key}); 8 | 9 | static const InterviewType interviewType = InterviewType.youtube; 10 | 11 | @override 12 | Widget build(BuildContext context, WidgetRef ref) { 13 | return InterviewIndicatorCard( 14 | showNewBadge: true, 15 | logoPath: interviewType.logoPath, 16 | title: tr(LocaleKeys.youtubeInterview_title), 17 | subDescription: tr(LocaleKeys.youtubeInterview_subDescription), 18 | onCardTapped: () { 19 | onYoutubeFeatureCardTapped(ref); 20 | }, 21 | onPlusSuffixedBtnTapped: () { 22 | onYoutubeFeatureCardTapped(ref); 23 | }, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat/providers/chat_scroll_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'chat_scroll_controller.g.dart'; 5 | 6 | @riverpod 7 | Raw chatScrollController(ChatScrollControllerRef ref) { 8 | return ScrollController(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat/providers/interview_result_page_view_controller_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'interview_result_page_view_controller_provider.g.dart'; 5 | 6 | @riverpod 7 | class InterviewResultPageViewController 8 | extends _$InterviewResultPageViewController { 9 | @override 10 | PageController build() { 11 | final controller = PageController(viewportFraction: 0.833); 12 | return controller; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat/providers/is_follow_up_process_active_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'is_follow_up_process_active_provider.g.dart'; 4 | 5 | /// 6 | /// 꼬리 질문 활성화 여부 7 | /// 8 | @riverpod 9 | class IsFollowUpProcessActive extends _$IsFollowUpProcessActive { 10 | @override 11 | bool build() { 12 | return true; 13 | } 14 | 15 | void toggle() { 16 | state = !state; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat/providers/recognized_text_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'recognized_text_provider.g.dart'; 4 | 5 | @riverpod 6 | class RecognizedText extends _$RecognizedText { 7 | @override 8 | String build() => ''; // 초기 값으로 빈 문자열 9 | 10 | void update(String newText) { 11 | state = newText; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat/providers/speech_mode_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'speech_mode_provider.g.dart'; 4 | 5 | @riverpod 6 | class IsSpeechMode extends _$IsSpeechMode { 7 | // ignore: avoid_public_notifier_properties 8 | bool? isPermissionGranted ; 9 | 10 | @override 11 | bool build() => false; 12 | 13 | void toggle() { 14 | state = !state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat/widgets/chat_page_watch_view.p.dart: -------------------------------------------------------------------------------- 1 | part of '../chat_page.dart'; 2 | 3 | class _WatchView extends ConsumerWidget { 4 | const _WatchView({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context, WidgetRef ref) { 8 | ref.watch(selectedChatRoomProvider); 9 | return const EmptyBox(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/chat_list/providers/chat_list_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/app/router/router.dart'; 3 | import 'package:techtalk/features/chat/chat.dart'; 4 | import 'package:techtalk/features/chat/repositories/enums/interview_type.enum.dart'; 5 | import 'package:techtalk/features/topic/repositories/entities/topic_entity.dart'; 6 | 7 | part 'chat_list_route_arg.g.dart'; 8 | 9 | @riverpod 10 | ChatListRouteArg chatListRouteArg(ChatListRouteArgRef ref) { 11 | return ChatListRoute.arg; 12 | } 13 | 14 | typedef ChatListRouteArg = ({ 15 | TopicEntity? topic, 16 | InterviewType interviewType, 17 | List? chatRooms 18 | }); 19 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/created_question_list/constant/created_question_list_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/interview/use_case/param/start_interview_flow_use_case_param.dart'; 2 | 3 | final class CreatedQuestionListRouteArg { 4 | final StartInterviewFlowBaseParam useCaseParma; 5 | 6 | CreatedQuestionListRouteArg(this.useCaseParma); 7 | } 8 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/created_question_list/provider/created_question_list_rout_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/presentation/pages/interview/created_question_list/constant/created_question_list_route_arg.dart'; 3 | 4 | part 'created_question_list_rout_arg_provider.g.dart'; 5 | 6 | @riverpod 7 | CreatedQuestionListRouteArg createdQuestionListRouteArg( 8 | CreatedQuestionListRouteArgRef ref) { 9 | throw Exception('CreatedQuestionListRouteArg > argument 초기화 시켜 주어야 합니다'); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/interview_level_selection/constant/interview_level_selection_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/interview/use_case/param/start_interview_flow_use_case_param.dart'; 2 | 3 | final class InterviewLevelSelectionRouteArg { 4 | final StartInterviewFlowBaseParam useCaseParam; 5 | 6 | InterviewLevelSelectionRouteArg(this.useCaseParam); 7 | } 8 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/interview_level_selection/interview_level_selection_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:techtalk/presentation/pages/interview/interview_level_selection/provider/level_selection_page_view_controller.dart'; 4 | 5 | mixin class InterviewLevelSelectionState { 6 | /// 7 | /// 페이지뷰 컨트롤러 8 | /// 9 | PageController pageController(WidgetRef ref) => 10 | ref.watch(levelSelectionPageViewControllerProvider); 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/interview_level_selection/provider/interview_level_selection_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | import 'package:techtalk/presentation/pages/interview/interview_level_selection/constant/interview_level_selection_route_arg.dart'; 4 | 5 | part 'interview_level_selection_route_arg_provider.g.dart'; 6 | 7 | @riverpod 8 | InterviewLevelSelectionRouteArg interviewLevelSelectionRouteArg(Ref ref) { 9 | throw Exception('argument 초기화 필요'); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/interview_level_selection/provider/level_selection_page_view_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'level_selection_page_view_controller.g.dart'; 5 | 6 | @riverpod 7 | class LevelSelectionPageViewController 8 | extends _$LevelSelectionPageViewController { 9 | @override 10 | Raw build() { 11 | final controller = PageController(initialPage: 1); 12 | ref.onDispose(controller.dispose); 13 | return controller; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/interview_level_selection/widgets/leading_view.p.dart: -------------------------------------------------------------------------------- 1 | part of '../interview_level_selection_page.dart'; 2 | 3 | class _LeadingView extends StatelessWidget { 4 | const _LeadingView({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Padding( 9 | padding: const EdgeInsets.symmetric(horizontal: 16), 10 | child: Text( 11 | tr(LocaleKeys.interview_interviewLevel_title), 12 | style: AppTextStyle.headline1, 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/proficiency_interview_topic_selection/constant/proficiency_interview_topic_selection_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/interview/use_case/param/start_interview_flow_use_case_param.dart'; 2 | 3 | final class ProficiencyInterviewTopicSelectionRouteArgument { 4 | final StartInterviewFlowBaseParam useCaseParam; 5 | 6 | ProficiencyInterviewTopicSelectionRouteArgument(this.useCaseParam); 7 | } 8 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/proficiency_interview_topic_selection/provider/proficiency_interview_topic_selection_route_arg_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | import 'package:techtalk/presentation/pages/interview/proficiency_interview_topic_selection/constant/proficiency_interview_topic_selection_route_arg.dart'; 4 | 5 | part 'proficiency_interview_topic_selection_route_arg_provide.g.dart'; 6 | 7 | @riverpod 8 | ProficiencyInterviewTopicSelectionRouteArgument 9 | proficiencyInterviewTopicSelectionRouteArg(Ref ref) { 10 | throw Exception('argument 초기화 필요'); 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/proficiency_interview_topic_selection/widgets/selected_tech_set_list_view.p.dart: -------------------------------------------------------------------------------- 1 | part of '../proficiency_interview_topic_selection_page.dart'; 2 | 3 | class _SelectedTechSetListView extends ConsumerWidget 4 | with 5 | ProficiencyInterviewTopicSelectionState, 6 | ProficiencyInterviewTopicSelectionEvent { 7 | const _SelectedTechSetListView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context, WidgetRef ref) { 11 | return TechSetListView( 12 | techSets: selectedTechSets(ref), 13 | height: 60, 14 | scrollController: context.getController(), 15 | onItemTapped: (item) { 16 | removeTechSetItemFromSelection(ref, techSet: item); 17 | }, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_count_select/constant/select_question_count_route_argument.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/chat/chat.dart'; 2 | import 'package:techtalk/features/interview/use_case/param/start_interview_flow_use_case_param.dart'; 3 | import 'package:techtalk/features/topic/topic.dart'; 4 | 5 | final class SelectQuestionCountRouteArg { 6 | final StartInterviewFlowBaseParam? useCaseParam; 7 | final InterviewType interviewType; 8 | final List topics; 9 | 10 | const SelectQuestionCountRouteArg({ 11 | this.useCaseParam, 12 | required this.interviewType, 13 | required this.topics, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_count_select/providers/select_question_count_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | import 'package:techtalk/presentation/pages/interview/question_count_select/constant/select_question_count_route_argument.dart'; 4 | 5 | part 'select_question_count_route_arg.g.dart'; 6 | 7 | @riverpod 8 | SelectQuestionCountRouteArg selectedQuestionCountRouteArg(Ref ref) { 9 | return throw Exception('argument를 초기화 시켜 주어야 합니다'); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_count_select/providers/selected_question_count_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/presentation/pages/interview/question_count_select/providers/select_question_count_route_arg.dart'; 3 | 4 | part 'selected_question_count_provider.g.dart'; 5 | 6 | @Riverpod(dependencies: [selectedQuestionCountRouteArg]) 7 | class SelectedQuestionCount extends _$SelectedQuestionCount { 8 | @override 9 | int build() { 10 | final arg = ref.read(selectedQuestionCountRouteArgProvider); 11 | return arg.interviewType.isProficiency ? 0 : 4; // 4,8개 12 | } 13 | 14 | void update(int count) { 15 | state = count; 16 | } 17 | 18 | static int defaultPlusCount = 4; 19 | } 20 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_count_select/question_count_select_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:techtalk/presentation/pages/interview/question_count_select/constant/select_question_count_route_argument.dart'; 3 | import 'package:techtalk/presentation/pages/interview/question_count_select/providers/select_question_count_route_arg.dart'; 4 | import 'package:techtalk/presentation/pages/interview/question_count_select/providers/selected_question_count_provider.dart'; 5 | 6 | mixin class QuestionCountSelectState { 7 | /// 8 | /// 선택된 문제 개수 9 | /// 10 | int selectedQuestionCount(WidgetRef ref) => 11 | ref.watch(selectedQuestionCountProvider); 12 | 13 | /// 14 | /// 라우팅 인자 15 | /// 16 | SelectQuestionCountRouteArg arg(WidgetRef ref) => 17 | ref.watch(selectedQuestionCountRouteArgProvider); 18 | } 19 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_creation/constant/question_creation_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/interview/use_case/param/start_interview_flow_use_case_param.dart'; 2 | 3 | final class QuestionCreationRouteArg { 4 | final StartInterviewFlowBaseParam useCaseParam; 5 | 6 | QuestionCreationRouteArg(this.useCaseParam); 7 | } 8 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_creation/provider/created_proficiency_qnas_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/chat/repositories/entities/proficiency_qna_entity.dart'; 3 | import 'package:techtalk/features/interview/use_case/create_proficiency_interview_qna_use_case.dart'; 4 | import 'package:techtalk/features/interview/use_case/param/start_interview_flow_use_case_param.dart'; 5 | 6 | part 'created_proficiency_qnas_provider.g.dart'; 7 | 8 | @riverpod 9 | class CreatedProficiencyQnas extends _$CreatedProficiencyQnas { 10 | @override 11 | Future> build( 12 | ProficiencyInterviewFlowParam useCaseParam) async { 13 | final result = 14 | await CreateProficiencyInterviewQnaUseCase().call(useCaseParam); 15 | return result; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_creation/provider/question_creation_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | import 'package:techtalk/presentation/pages/interview/question_creation/constant/question_creation_route_arg.dart'; 4 | 5 | part 'question_creation_route_arg_provider.g.dart'; 6 | 7 | @riverpod 8 | QuestionCreationRouteArg questionCreationRouteArg(Ref ref) { 9 | throw Exception('ProficiencyQuestionCreationRouteArg > argument 초기화 필요'); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/question_creation/widgets/scaffold.p.dart: -------------------------------------------------------------------------------- 1 | part of '../question_creation_page.dart'; 2 | 3 | class _Scaffold extends StatelessWidget { 4 | const _Scaffold({ 5 | super.key, 6 | required this.leadingView, 7 | required this.illustView, 8 | }); 9 | 10 | final Widget leadingView; 11 | final Widget illustView; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Column( 16 | crossAxisAlignment: CrossAxisAlignment.start, 17 | children: [ 18 | const Gap(16), 19 | leadingView, 20 | const Spacer( 21 | flex: 104, 22 | ), 23 | illustView, 24 | const Spacer( 25 | flex: 166, 26 | ), 27 | ], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/select_common_question/constant/select_common_question_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/features/chat/chat.dart'; 2 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 3 | import 'package:techtalk/features/topic/repositories/entities/common_qna_entity.dart'; 4 | import 'package:techtalk/features/topic/repositories/entities/topic_entity.dart'; 5 | 6 | final class SelectCommonQuestionRouteArg { 7 | final List topics; 8 | 9 | const SelectCommonQuestionRouteArg({ 10 | required this.topics, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/select_common_question/provider/select_common_question_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | import 'package:techtalk/presentation/pages/interview/select_common_question/constant/select_common_question_route_arg.dart'; 4 | 5 | part 'select_common_question_route_arg_provider.g.dart'; 6 | 7 | @riverpod 8 | SelectCommonQuestionRouteArg selectCommonQuestionRouteArg(Ref ref) { 9 | throw Exception('인자를 초기화 시켜 주어야 합니다'); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/select_common_question/provider/selected_interview_topic_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/topic/repositories/entities/topic_entity.dart'; 3 | 4 | part 'selected_interview_topic_provider.g.dart'; 5 | 6 | @riverpod 7 | class SelectedInterviewTopic extends _$SelectedInterviewTopic { 8 | @override 9 | TopicEntity build(TopicEntity topic) { 10 | return topic; 11 | } 12 | 13 | void update(TopicEntity topic) { 14 | if (state == topic) return; 15 | state = topic; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/select_commotion_interview_type/selected_common_interview_type_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:techtalk/app/router/router.dart'; 3 | import 'package:techtalk/features/chat/chat.dart'; 4 | 5 | mixin SelectedCommonInterviewTypeEvent { 6 | /// 7 | /// 면접 주제 선택 페이지로 이동 8 | /// 9 | void routeToTopicSelectPage(BuildContext context, 10 | {required InterviewType type}) { 11 | InterviewTopicSelectRoute(type.name).push(context); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/topic_select/providers/interview_topic_select_route_arg.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/app/router/router.dart'; 3 | import 'package:techtalk/features/chat/chat.dart'; 4 | 5 | part 'interview_topic_select_route_arg.g.dart'; 6 | 7 | @riverpod 8 | InterviewType interviewTopicSelectRouteArg( 9 | InterviewTopicSelectRouteArgRef ref) { 10 | return InterviewTopicSelectRoute.arg; 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/interview/topic_select/providers/interview_topic_select_scroll_controller_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'interview_topic_select_scroll_controller_provider.g.dart'; 5 | 6 | @riverpod 7 | Raw interviewTopicSelectScrollController( 8 | InterviewTopicSelectScrollControllerRef ref) { 9 | return ScrollController(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/main/main_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:techtalk/presentation/pages/main/provider/show_new_feature_indicator_provider.dart'; 3 | 4 | mixin class MainState { 5 | /// 6 | /// 유튜브 신기능 말풍선 노출 여부 7 | /// 8 | bool showNewFeatureIndicator(WidgetRef ref) { 9 | return ref.watch(showNewFeatureIndicatorProvider); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/my_info/job_group_setting/provider/selected_job_groups_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 3 | import 'package:techtalk/presentation/providers/user/user_info_provider.dart'; 4 | 5 | part 'selected_job_groups_provider.g.dart'; 6 | 7 | @riverpod 8 | class SelectedJobGroups extends _$SelectedJobGroups { 9 | @override 10 | List build() { 11 | final userJobGroups = ref.read(userInfoProvider).value?.jobGroups ?? []; 12 | 13 | return userJobGroups.toList(); 14 | } 15 | 16 | void add(JobGroupEntity item) { 17 | state = [...state, item]; 18 | } 19 | 20 | void remove(JobGroupEntity item) { 21 | final removeList = state..remove(item); 22 | state = [...removeList]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/presentation/pages/my_info/my_youtube_board/constant/youtube_board_tab_type.enum.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/localization/locale_keys.g.dart'; 2 | 3 | enum YoutubeBoardTabType { 4 | watchHistory(LocaleKeys.myInfo_watchHistory), 5 | bookmark(LocaleKeys.myInfo_favorites), 6 | uploaded(LocaleKeys.myInfo_uploadedVideos); 7 | 8 | final String label; 9 | 10 | const YoutubeBoardTabType(this.label); 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/my_info/profile_setting/providers/profile_setting_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/user/user.dart'; 3 | 4 | part 'profile_setting_route_arg_provider.g.dart'; 5 | 6 | @riverpod 7 | UserEntity profileSettingRouteArg(ProfileSettingRouteArgRef ref) { 8 | throw UnimplementedError('임시'); 9 | } 10 | -------------------------------------------------------------------------------- /lib/presentation/pages/sign_up/providers/sign_up_jobs_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/core/constants/job_group.enum.dart'; 3 | 4 | part 'sign_up_jobs_provider.g.dart'; 5 | 6 | @riverpod 7 | class SignUpJobs extends _$SignUpJobs { 8 | @override 9 | List build() { 10 | return []; 11 | } 12 | 13 | void toggle(JobGroupTypes item) { 14 | if (state.contains(item)) { 15 | state = state.toList()..remove(item); 16 | } else { 17 | state = state.toList()..add(item); 18 | } 19 | } 20 | 21 | void removeAt(int index) { 22 | state = state.toList()..removeAt(index); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/presentation/pages/sign_up/providers/sign_up_topics_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/topic/topic.dart'; 3 | 4 | part 'sign_up_topics_provider.g.dart'; 5 | 6 | @riverpod 7 | class SignUpTopics extends _$SignUpTopics { 8 | @override 9 | List build() => []; 10 | 11 | void add(TopicEntity item) { 12 | state = state.toList()..add(item); 13 | } 14 | 15 | void remove(TopicEntity item) { 16 | state = state.toList()..remove(item); 17 | } 18 | 19 | void removeAt(int index) { 20 | state = state.toList()..removeAt(index); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/pages/splash/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | import 'package:techtalk/core/index.dart'; 5 | import 'package:techtalk/presentation/pages/splash/splash_event.dart'; 6 | import 'package:techtalk/presentation/widgets/base/base_page.dart'; 7 | 8 | class SplashPage extends BasePage with SplashEvent { 9 | const SplashPage({super.key}); 10 | 11 | @override 12 | void onInit(WidgetRef ref) { 13 | routeByUserAuthAndData(ref); 14 | } 15 | 16 | @override 17 | Widget buildPage(BuildContext context, WidgetRef ref) { 18 | return Center( 19 | child: SvgPicture.asset( 20 | Assets.characterBlue04, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/presentation/pages/study/learning/providers/current_study_qna_index_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/presentation/pages/study/learning/providers/study_qna_controller.dart'; 3 | 4 | part 'current_study_qna_index_provider.g.dart'; 5 | 6 | @riverpod 7 | int currentStudyQnaIndex(CurrentStudyQnaIndexRef ref) { 8 | try { 9 | return ref.watch(studyQnaControllerProvider).page?.round() ?? 0; 10 | } catch (e) { 11 | return 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/pages/study/learning/providers/study_answer_blur_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'study_answer_blur_provider.g.dart'; 4 | 5 | @Riverpod(keepAlive: true) 6 | class StudyAnswerBlur extends _$StudyAnswerBlur { 7 | @override 8 | bool build() => false; 9 | 10 | void toggle() => state = !state; 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/study/learning/providers/study_bookmark_filter_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'study_bookmark_filter_provider.g.dart'; 4 | 5 | @Riverpod(keepAlive: true) 6 | class StudyBookmarkFilter extends _$StudyBookmarkFilter { 7 | @override 8 | bool build() => false; 9 | 10 | void toggle() => state = !state; 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/study/topic_selection/providers/selected_study_topic_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/app/router/router.dart'; 3 | import 'package:techtalk/features/topic/topic.dart'; 4 | 5 | part 'selected_study_topic_provider.g.dart'; 6 | 7 | @riverpod 8 | TopicEntity selectedStudyTopic(SelectedStudyTopicRef ref) { 9 | return StudyRoute.arg; 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/study/topic_selection/providers/study_topic_selection_scroll_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'study_topic_selection_scroll_controller.g.dart'; 5 | 6 | @riverpod 7 | Raw studyTopicSelectionScrollController( 8 | StudyTopicSelectionScrollControllerRef ref) { 9 | return ScrollController(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/study/topic_selection/providers/study_topic_selection_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:techtalk/features/topic/topic.dart'; 4 | import 'package:techtalk/presentation/pages/study/topic_selection/providers/study_topic_selection_scroll_controller.dart'; 5 | import 'package:techtalk/presentation/providers/topic/sorted_topics_provider.dart'; 6 | 7 | mixin class StudyTopicSelectionState { 8 | /// 9 | /// 스크롤 컨트롤러 10 | /// 11 | ScrollController scrollController(WidgetRef ref) => 12 | ref.watch(studyTopicSelectionScrollControllerProvider); 13 | 14 | /// 15 | /// 면접 주제 리스트 16 | /// 17 | List topics(WidgetRef ref) => ref.watch(sortedTopicsProvider); 18 | } 19 | -------------------------------------------------------------------------------- /lib/presentation/pages/wrong_answer_note/providers/selected_wrong_answer_topic_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/topic/topic.dart'; 3 | import 'package:techtalk/presentation/providers/user/user_topics_provider.dart'; 4 | 5 | part 'selected_wrong_answer_topic_provider.g.dart'; 6 | 7 | @riverpod 8 | class SelectedWrongAnswerTopic extends _$SelectedWrongAnswerTopic { 9 | @override 10 | TopicEntity? build() { 11 | final targetTopics = ref.watch(userTopicsProvider); 12 | 13 | return targetTopics.isNotEmpty ? targetTopics.first : null; 14 | } 15 | 16 | void updateTopic(TopicEntity value) => state = value; 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/pages/wrong_answer_note/providers/wrong_answer_blur_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'wrong_answer_blur_provider.g.dart'; 4 | 5 | @riverpod 6 | class WrongAnswerBlur extends _$WrongAnswerBlur { 7 | @override 8 | bool build() { 9 | return false; 10 | } 11 | 12 | void toggle() { 13 | state = !state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/pages/wrong_answer_note/providers/wrong_answer_note_scroll_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'wrong_answer_note_scroll_controller.g.dart'; 5 | 6 | @riverpod 7 | class WrongAnswerNoteScrollController 8 | extends _$WrongAnswerNoteScrollController { 9 | static double animatedOffset = 20; 10 | 11 | @override 12 | Raw build() { 13 | return ScrollController(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/pages/wrong_answer_note/providers/wrong_answers_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/core/index.dart'; 3 | import 'package:techtalk/features/topic/topic.dart'; 4 | 5 | part 'wrong_answers_provider.g.dart'; 6 | 7 | @Riverpod(keepAlive: true) 8 | class WrongAnswers extends _$WrongAnswers { 9 | @override 10 | FutureOr> build(String topicId) async { 11 | final response = await getWrongAnswersUseCase(topicId); 12 | 13 | return response.fold( 14 | onSuccess: (wrongAnswers) { 15 | return wrongAnswers; 16 | }, 17 | onFailure: (e) { 18 | SnackBarService.showSnackBar((e as CustomException).message); 19 | throw e; 20 | }, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/channel_detail/provider/channel_detail_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 4 | import 'package:techtalk/features/youtube/index.dart'; 5 | import 'package:techtalk/features/youtube/repositories/entities/channel_detail_entity.dart'; 6 | 7 | part 'channel_detail_provider.g.dart'; 8 | 9 | @riverpod 10 | class ChannelDetail extends _$ChannelDetail { 11 | @override 12 | Future build(String channelId) async { 13 | final response = await youtubeRepository.getChannelDetail(channelId); 14 | return response.fold( 15 | onSuccess: (channel) => channel, 16 | onFailure: (e) { 17 | log('$this > 채널 상세 정보 호출 실패 : $e'); 18 | throw e; 19 | }, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/channel_detail/provider/channel_detail_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/youtube/repositories/entities/channel_entity.dart'; 3 | 4 | part 'channel_detail_route_arg_provider.g.dart'; 5 | 6 | @riverpod 7 | ChannelDetailRouteArg channelDetailRouteArg(ChannelDetailRouteArgRef ref) { 8 | throw Exception('channelDetailRouteArg > argument를 초기화 해주어야 합니다.'); 9 | } 10 | 11 | final class ChannelDetailRouteArg { 12 | final ChannelEntity channel; 13 | final String currentContentId; 14 | 15 | ChannelDetailRouteArg( 16 | {required this.channel, required this.currentContentId}); 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/channel_detail/widgets/channel_info_view.p.dart: -------------------------------------------------------------------------------- 1 | part of '../channel_detail_page.dart'; 2 | 3 | class _ChannelInfoView extends ConsumerWidget with ChannelDetailState { 4 | const _ChannelInfoView({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context, WidgetRef ref) { 8 | return Row( 9 | children: [ 10 | RoundProfileImg( 11 | size: 64, 12 | imgUrl: channel(ref).logoUrl, 13 | ), 14 | const Gap(12), 15 | Column( 16 | crossAxisAlignment: CrossAxisAlignment.start, 17 | children: [ 18 | Text( 19 | channel(ref).name, 20 | style: AppTextStyle.headline2, 21 | ), 22 | ], 23 | ), 24 | ], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/providers/fab_scroll_expose_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | 5 | final class FabScrollExposeNotifier extends ChangeNotifier {} 6 | 7 | final fabScrollExposeNotifierProvider = 8 | AutoDisposeChangeNotifierProvider((ref) => FabScrollExposeNotifier()); 9 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/providers/is_interview_progress_ready_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/presentation/pages/youtube/detail/providers/youtube_content_qna_provider.dart'; 3 | import 'package:techtalk/presentation/pages/youtube/detail/providers/youtube_detail_route_arg_provider.dart'; 4 | 5 | part 'is_interview_progress_ready_provider.g.dart'; 6 | 7 | @Riverpod(dependencies: [youtubeDetailRouteArg, YoutubeContentQna]) 8 | class IsInterviewProgressReady extends _$IsInterviewProgressReady { 9 | @override 10 | bool build() { 11 | final arg = ref.read(youtubeDetailRouteArgProvider); 12 | 13 | final async = ref.watch(youtubeContentQnaProvider( 14 | contentId: arg.contentId, 15 | )); 16 | 17 | if (async.isLoading) return true; 18 | return async.value?.any((e) => e.isSelected) ?? true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/providers/related_youtube_videos_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 4 | import 'package:techtalk/features/youtube/index.dart'; 5 | import 'package:techtalk/features/youtube/repositories/entities/video_overview_entity.dart'; 6 | 7 | part 'related_youtube_videos_provider.g.dart'; 8 | 9 | @riverpod 10 | class RelatedYoutubeVideo extends _$RelatedYoutubeVideo { 11 | @override 12 | FutureOr> build(String contentId) async { 13 | final response = await youtubeRepository.getRelatedVideo(contentId); 14 | return response.fold( 15 | onSuccess: (videos) { 16 | return videos; 17 | }, 18 | onFailure: (e) { 19 | log('유튜브 관련 정보 호출 실패 : ${e}'); 20 | throw e; 21 | }, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/providers/youtube_main_info_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 4 | import 'package:techtalk/features/youtube/index.dart'; 5 | 6 | part 'youtube_main_info_provider.g.dart'; 7 | 8 | @riverpod 9 | class YoutubeMainInfo extends _$YoutubeMainInfo { 10 | @override 11 | Future build(String contentId) async { 12 | final response = 13 | await youtubeRepository.getYoutubeMainInfo(contentId: contentId); 14 | 15 | return response.fold( 16 | onSuccess: (info) => info, 17 | onFailure: (e) { 18 | log('YoutubeMainInfo 호출 실패 : $e'); 19 | throw e; 20 | }, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/providers/youtube_summary_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 4 | import 'package:techtalk/features/youtube/index.dart'; 5 | 6 | part 'youtube_summary_provider.g.dart'; 7 | 8 | @riverpod 9 | class YoutubeSummary extends _$YoutubeSummary { 10 | @override 11 | FutureOr build(String contentId) async { 12 | final response = await youtubeRepository.getYoutubeSummary(contentId); 13 | 14 | return response.fold(onSuccess: (e) { 15 | return e; 16 | }, onFailure: (e) { 17 | log('유튜브 요약 정보 호출 실패 : $e'); 18 | throw e; 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/providers/youtube_video_data_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 4 | import 'package:techtalk/features/youtube/index.dart'; 5 | 6 | part 'youtube_video_data_provider.g.dart'; 7 | 8 | @riverpod 9 | class YoutubeVideoData extends _$YoutubeVideoData { 10 | @override 11 | Future build(String videoId) async { 12 | final result = await getYoutubeVideoDataUseCase.call(videoId); 13 | return result.fold( 14 | onSuccess: (data) => data, 15 | onFailure: (e) { 16 | log(e.toString()); 17 | throw e; 18 | }, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/widgets/app_bar.p.dart: -------------------------------------------------------------------------------- 1 | part of '../youtube_detail_page.dart'; 2 | 3 | class _AppBar extends ConsumerWidget 4 | with YoutubeDetailState, YoutubeDetailEvent { 5 | const _AppBar({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context, WidgetRef ref) { 9 | return EmptyBox(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/widgets/constants/contents_detail_tab_type.enum.dart: -------------------------------------------------------------------------------- 1 | import 'package:techtalk/app/localization/locale_keys.g.dart'; 2 | 3 | enum ContentsDetailTabType { 4 | summary(LocaleKeys.youtubeDetail_summary), 5 | questions(LocaleKeys.youtubeDetail_interview); 6 | 7 | final String displayStr; 8 | 9 | const ContentsDetailTabType(this.displayStr); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/detail/widgets/section_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:gap/gap.dart'; 4 | import 'package:techtalk/app/style/app_text_style.dart'; 5 | 6 | class SectionTitle extends StatelessWidget { 7 | const SectionTitle({super.key, required this.title, required this.iconPath}); 8 | 9 | final String title; 10 | final String iconPath; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Row( 15 | children: [ 16 | SvgPicture.asset(iconPath), 17 | const Gap(2), 18 | Text( 19 | title, 20 | style: AppTextStyle.headline2, 21 | ), 22 | ], 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/upload/analyze_youtube/analyze_youtube_progerss_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:techtalk/presentation/providers/system/notification_status_provider.dart'; 3 | 4 | mixin class AnalyzeYoutubeProgressState { 5 | /// 6 | /// 알람 권한 허용 여부 7 | /// 8 | AsyncValue isNotificationGranted(WidgetRef ref) => 9 | ref.watch(notificationStatusProvider); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/upload/submitted_youtube_confirm/submitted_youtube_confirm_state.dart: -------------------------------------------------------------------------------- 1 | mixin class SubmittedYoutubeConfirmState { 2 | // /// 3 | // /// 업로드할 유튜브 콘텐츠 4 | // /// 5 | // AsyncValue submittedYoutube(WidgetRef ref) { 6 | // final passedVideo = ref.read(submittedYoutubeConfirmArgProvider); 7 | // 8 | // } 9 | } 10 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/upload/youtube_link_submit/provider/youtube_link_input_controller_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'youtube_link_input_controller_provider.g.dart'; 5 | 6 | @riverpod 7 | class YoutubeLinkInputController extends _$YoutubeLinkInputController { 8 | final GlobalKey formKey = GlobalKey(); 9 | 10 | @override 11 | Raw build() { 12 | final controller = TextEditingController(); 13 | ref.onDispose(controller.dispose); 14 | 15 | return controller; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/pages/youtube/upload_failed/provider/youtube_upload_failed_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/youtube/index.dart'; 3 | import 'package:youtube_explode_dart/youtube_explode_dart.dart'; 4 | 5 | part 'youtube_upload_failed_route_arg_provider.g.dart'; 6 | 7 | @riverpod 8 | YoutubeUploadFailedArg youtubeUploadFailedRouteArg( 9 | YoutubeUploadFailedRouteArgRef ref) { 10 | throw Exception('youtubeUploadFailedRouteArg : arugment를 초기화 시켜주어야 합니다'); 11 | } 12 | 13 | final class YoutubeUploadFailedArg { 14 | final YoutubeUploadFailedType type; 15 | final Video? video; 16 | 17 | YoutubeUploadFailedArg({required this.type, this.video}); 18 | } 19 | -------------------------------------------------------------------------------- /lib/presentation/providers/scroll/selected_job_group_scroll_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'selected_job_group_scroll_controller.g.dart'; 5 | 6 | @riverpod 7 | Raw selectedJobGroupScrollController( 8 | SelectedJobGroupScrollControllerRef ref) { 9 | return ScrollController(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/providers/scroll/selected_skill_scroll_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'selected_skill_scroll_controller.g.dart'; 5 | 6 | @riverpod 7 | Raw selectedSkillScrollController( 8 | SelectedSkillScrollControllerRef ref) { 9 | return ScrollController(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/providers/system/app_version_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/core/services/snack_bar_service.dart'; 3 | import 'package:techtalk/features/system/system.dart'; 4 | 5 | part 'app_version_provider.g.dart'; 6 | 7 | /// 8 | /// 앱의 버전 정보를 관리하는 provider 9 | /// 10 | @Riverpod(keepAlive: true) 11 | class AppVersion extends _$AppVersion { 12 | @override 13 | FutureOr build() async { 14 | final response = await getVersionInfoUseCase.call(); 15 | 16 | return response.fold( 17 | onSuccess: (versionInfo) { 18 | return versionInfo; 19 | }, 20 | onFailure: (e) { 21 | SnackBarService.showSnackBar('앱의 버전 정보를 가져오는데 실패하였습니다.'); 22 | throw e; 23 | }, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/presentation/widgets/base/base_new_page.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/presentation/widgets/base/controller_holder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ControllerHolder extends InheritedWidget { 4 | final T controller; 5 | 6 | const ControllerHolder({ 7 | required this.controller, 8 | super.key, 9 | required super.child, 10 | }); 11 | 12 | static ControllerHolder? maybeOf(BuildContext context) { 13 | return context.dependOnInheritedWidgetOfExactType>(); 14 | } 15 | 16 | @override 17 | bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; 18 | } 19 | 20 | extension ControllerHolderContextExt on BuildContext { 21 | T getController() { 22 | final argument = ControllerHolder.maybeOf(this)?.controller; 23 | if (argument == null) { 24 | throw Exception('잘못된 컨트롤러 값 입니다'); 25 | } 26 | return argument; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/presentation/widgets/base/index.dart: -------------------------------------------------------------------------------- 1 | export 'base_page.dart'; 2 | export 'base_view.dart'; 3 | export 'base_statless_page.dart'; 4 | export 'base_new_page.dart'; -------------------------------------------------------------------------------- /lib/presentation/widgets/common/box/empty_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class SquareBox extends SizedBox { 4 | const SquareBox( 5 | double dimension, [ 6 | Key? key, 7 | ]) : super.square( 8 | key: key, 9 | dimension: dimension, 10 | ); 11 | } 12 | 13 | class EmptyBox extends SizedBox { 14 | const EmptyBox({super.key}) : super.shrink(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/button/app_back_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | import 'package:techtalk/app/style/app_color.dart'; 5 | import 'package:techtalk/core/constants/assets.dart'; 6 | import 'package:techtalk/presentation/widgets/common/button/icon_flash_area_button.dart'; 7 | 8 | class AppBackButton extends StatelessWidget { 9 | const AppBackButton({ 10 | super.key, 11 | this.onBackBtnTapped, 12 | }); 13 | 14 | final VoidCallback? onBackBtnTapped; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return IconButton( 19 | onPressed: onBackBtnTapped ?? context.pop, 20 | icon: SvgPicture.asset( 21 | Assets.iconsIconAppBarLeft, 22 | height: 24, 23 | width: 24, 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/divider/list_view_divider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:techtalk/app/style/app_color.dart'; 3 | 4 | class ListViewDivider extends StatelessWidget { 5 | const ListViewDivider({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Divider( 10 | color: AppColor.of.gray2, 11 | height: 32, 12 | thickness: 0.7, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/indicator/tecktalk_refresh_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:techtalk/app/style/app_color.dart'; 3 | 4 | class TechtalkRefreshIndicator extends StatelessWidget { 5 | const TechtalkRefreshIndicator({ 6 | super.key, 7 | required this.onRefresh, 8 | required this.child, 9 | }); 10 | 11 | final Widget child; 12 | final Future Function() onRefresh; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return RefreshIndicator( 17 | backgroundColor: AppColor.of.white, 18 | color: AppColor.of.blue3, 19 | onRefresh: onRefresh, 20 | child: child, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/state/keep_alive_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class KeepAliveView extends StatefulWidget { 4 | final Widget child; 5 | 6 | const KeepAliveView({Key? key, required this.child}) : super(key: key); 7 | 8 | @override 9 | State createState() => _KeepAliveViewState(); 10 | } 11 | 12 | class _KeepAliveViewState extends State 13 | with AutomaticKeepAliveClientMixin { 14 | @override 15 | bool get wantKeepAlive => true; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | super.build(context); 20 | return widget.child; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/text/bullet_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:techtalk/core/index.dart'; 4 | 5 | class BulletText extends StatelessWidget { 6 | const BulletText( 7 | this.text, { 8 | Key? key, 9 | this.style, 10 | }) : super(key: key); 11 | 12 | final String text; 13 | final TextStyle? style; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Row( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | SvgPicture.asset( 21 | Assets.iconsBullet, 22 | ), 23 | // const Text("• "), 24 | Expanded( 25 | child: Text( 26 | text, 27 | style: style, 28 | ), 29 | ), 30 | ], 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/presentation/widgets/section/tech_selection_bottom_sheet/provider/tech_set_selection_bottom_sheet_route_arg_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:techtalk/features/tech_set/repositories/entities/tech_set_entity.dart'; 3 | 4 | part 'tech_set_selection_bottom_sheet_route_arg_provider.g.dart'; 5 | 6 | @riverpod 7 | List techSetSelectionBottomSheetRouteArg( 8 | TechSetSelectionBottomSheetRouteArgRef ref, 9 | ) { 10 | throw Exception('argument를 초기화 시켜 주어야 합니다.'); 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/widgets/section/tech_selection_bottom_sheet/widgets/selected_tech_set_list_view.p.dart: -------------------------------------------------------------------------------- 1 | part of '../tech_set_selection_bottom_sheet.dart'; 2 | 3 | class _SelectedTechSetListView extends ConsumerWidget 4 | with TechSelectionBottomSheetState, TechSelectionBottomSheetEvent { 5 | const _SelectedTechSetListView({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context, WidgetRef ref) { 9 | return TechSetListView( 10 | techSets: selectedTechSets(ref), 11 | scrollController: scrollController(ref), 12 | onItemTapped: (item) { 13 | onSelectedTechSetTapped(ref, techSet: item); 14 | }, 15 | ); 16 | } 17 | } 18 | --------------------------------------------------------------------------------