Compare commits

...

2 Commits

49 changed files with 4791 additions and 110 deletions

740
.idea/libraries/Dart_Packages.xml generated Normal file
View File

@ -0,0 +1,740 @@
<component name="libraryTable">
<library name="Dart Packages" type="DartPackagesLibraryType">
<properties>
<option name="packageNameToDirsMap">
<entry key="_flutterfire_internals">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.59/lib" />
</list>
</value>
</entry>
<entry key="args">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/lib" />
</list>
</value>
</entry>
<entry key="async">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/async-2.13.0/lib" />
</list>
</value>
</entry>
<entry key="boolean_selector">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.2/lib" />
</list>
</value>
</entry>
<entry key="characters">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/characters-1.4.0/lib" />
</list>
</value>
</entry>
<entry key="clock">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/clock-1.1.2/lib" />
</list>
</value>
</entry>
<entry key="code_assets">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/code_assets-1.0.0/lib" />
</list>
</value>
</entry>
<entry key="collection">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/collection-1.19.1/lib" />
</list>
</value>
</entry>
<entry key="crypto">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/crypto-3.0.7/lib" />
</list>
</value>
</entry>
<entry key="csslib">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/csslib-1.0.2/lib" />
</list>
</value>
</entry>
<entry key="cupertino_icons">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cupertino_icons-1.0.8/lib" />
</list>
</value>
</entry>
<entry key="dio">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio-5.9.0/lib" />
</list>
</value>
</entry>
<entry key="dio_web_adapter">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio_web_adapter-2.1.1/lib" />
</list>
</value>
</entry>
<entry key="fake_async">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/fake_async-1.3.3/lib" />
</list>
</value>
</entry>
<entry key="ffi">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/ffi-2.1.5/lib" />
</list>
</value>
</entry>
<entry key="file">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file-7.0.1/lib" />
</list>
</value>
</entry>
<entry key="firebase_auth">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_auth-5.7.0/lib" />
</list>
</value>
</entry>
<entry key="firebase_auth_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_auth_platform_interface-7.7.3/lib" />
</list>
</value>
</entry>
<entry key="firebase_auth_web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_auth_web-5.15.3/lib" />
</list>
</value>
</entry>
<entry key="firebase_core">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core-3.15.2/lib" />
</list>
</value>
</entry>
<entry key="firebase_core_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_platform_interface-6.0.2/lib" />
</list>
</value>
</entry>
<entry key="firebase_core_web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_web-2.24.1/lib" />
</list>
</value>
</entry>
<entry key="flutter">
<value>
<list>
<option value="$PROJECT_DIR$/../../flutter/packages/flutter/lib" />
</list>
</value>
</entry>
<entry key="flutter_lints">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0/lib" />
</list>
</value>
</entry>
<entry key="flutter_secure_storage">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage-9.2.4/lib" />
</list>
</value>
</entry>
<entry key="flutter_secure_storage_linux">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/lib" />
</list>
</value>
</entry>
<entry key="flutter_secure_storage_macos">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/lib" />
</list>
</value>
</entry>
<entry key="flutter_secure_storage_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_platform_interface-1.1.2/lib" />
</list>
</value>
</entry>
<entry key="flutter_secure_storage_web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/lib" />
</list>
</value>
</entry>
<entry key="flutter_secure_storage_windows">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/lib" />
</list>
</value>
</entry>
<entry key="flutter_svg">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_svg-2.2.3/lib" />
</list>
</value>
</entry>
<entry key="flutter_test">
<value>
<list>
<option value="$PROJECT_DIR$/../../flutter/packages/flutter_test/lib" />
</list>
</value>
</entry>
<entry key="flutter_web_plugins">
<value>
<list>
<option value="$PROJECT_DIR$/../../flutter/packages/flutter_web_plugins/lib" />
</list>
</value>
</entry>
<entry key="glob">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/glob-2.1.3/lib" />
</list>
</value>
</entry>
<entry key="google_identity_services_web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib" />
</list>
</value>
</entry>
<entry key="google_sign_in">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in-6.3.0/lib" />
</list>
</value>
</entry>
<entry key="google_sign_in_android">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_android-6.2.1/lib" />
</list>
</value>
</entry>
<entry key="google_sign_in_ios">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_ios-5.9.0/lib" />
</list>
</value>
</entry>
<entry key="google_sign_in_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_platform_interface-2.5.0/lib" />
</list>
</value>
</entry>
<entry key="google_sign_in_web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_web-0.12.4+4/lib" />
</list>
</value>
</entry>
<entry key="hooks">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/hooks-1.0.0/lib" />
</list>
</value>
</entry>
<entry key="html">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/html-0.15.6/lib" />
</list>
</value>
</entry>
<entry key="http">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http-1.6.0/lib" />
</list>
</value>
</entry>
<entry key="http_parser">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.1.2/lib" />
</list>
</value>
</entry>
<entry key="js">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/js-0.6.7/lib" />
</list>
</value>
</entry>
<entry key="leak_tracker">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker-11.0.2/lib" />
</list>
</value>
</entry>
<entry key="leak_tracker_flutter_testing">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10/lib" />
</list>
</value>
</entry>
<entry key="leak_tracker_testing">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-3.0.2/lib" />
</list>
</value>
</entry>
<entry key="lints">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.0.0/lib" />
</list>
</value>
</entry>
<entry key="logging">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/logging-1.3.0/lib" />
</list>
</value>
</entry>
<entry key="matcher">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/matcher-0.12.17/lib" />
</list>
</value>
</entry>
<entry key="material_color_utilities">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/material_color_utilities-0.11.1/lib" />
</list>
</value>
</entry>
<entry key="meta">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0/lib" />
</list>
</value>
</entry>
<entry key="mime">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/mime-2.0.0/lib" />
</list>
</value>
</entry>
<entry key="native_toolchain_c">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/native_toolchain_c-0.17.4/lib" />
</list>
</value>
</entry>
<entry key="objective_c">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/objective_c-9.2.3/lib" />
</list>
</value>
</entry>
<entry key="path">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path-1.9.1/lib" />
</list>
</value>
</entry>
<entry key="path_parsing">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_parsing-1.1.0/lib" />
</list>
</value>
</entry>
<entry key="path_provider">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider-2.1.5/lib" />
</list>
</value>
</entry>
<entry key="path_provider_android">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_android-2.2.22/lib" />
</list>
</value>
</entry>
<entry key="path_provider_foundation">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_foundation-2.6.0/lib" />
</list>
</value>
</entry>
<entry key="path_provider_linux">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/lib" />
</list>
</value>
</entry>
<entry key="path_provider_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_platform_interface-2.1.2/lib" />
</list>
</value>
</entry>
<entry key="path_provider_windows">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/lib" />
</list>
</value>
</entry>
<entry key="petitparser">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/petitparser-7.0.1/lib" />
</list>
</value>
</entry>
<entry key="platform">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/platform-3.1.6/lib" />
</list>
</value>
</entry>
<entry key="plugin_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib" />
</list>
</value>
</entry>
<entry key="pub_semver">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/pub_semver-2.2.0/lib" />
</list>
</value>
</entry>
<entry key="sky_engine">
<value>
<list>
<option value="$PROJECT_DIR$/../../flutter/bin/cache/pkg/sky_engine/lib" />
</list>
</value>
</entry>
<entry key="source_span">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.1/lib" />
</list>
</value>
</entry>
<entry key="stack_trace">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.12.1/lib" />
</list>
</value>
</entry>
<entry key="stream_channel">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stream_channel-2.1.4/lib" />
</list>
</value>
</entry>
<entry key="string_scanner">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/string_scanner-1.4.1/lib" />
</list>
</value>
</entry>
<entry key="term_glyph">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/term_glyph-1.2.2/lib" />
</list>
</value>
</entry>
<entry key="test_api">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.7.7/lib" />
</list>
</value>
</entry>
<entry key="typed_data">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/lib" />
</list>
</value>
</entry>
<entry key="vector_graphics">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics-1.1.19/lib" />
</list>
</value>
</entry>
<entry key="vector_graphics_codec">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics_codec-1.1.13/lib" />
</list>
</value>
</entry>
<entry key="vector_graphics_compiler">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics_compiler-1.1.19/lib" />
</list>
</value>
</entry>
<entry key="vector_math">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0/lib" />
</list>
</value>
</entry>
<entry key="video_player">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player-2.10.1/lib" />
</list>
</value>
</entry>
<entry key="video_player_android">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_android-2.9.1/lib" />
</list>
</value>
</entry>
<entry key="video_player_avfoundation">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_avfoundation-2.8.10/lib" />
</list>
</value>
</entry>
<entry key="video_player_platform_interface">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_platform_interface-6.6.0/lib" />
</list>
</value>
</entry>
<entry key="video_player_web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_web-2.4.0/lib" />
</list>
</value>
</entry>
<entry key="vm_service">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vm_service-15.0.2/lib" />
</list>
</value>
</entry>
<entry key="web">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/web-1.1.1/lib" />
</list>
</value>
</entry>
<entry key="win32">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/win32-5.15.0/lib" />
</list>
</value>
</entry>
<entry key="xdg_directories">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xdg_directories-1.1.0/lib" />
</list>
</value>
</entry>
<entry key="xml">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xml-6.6.1/lib" />
</list>
</value>
</entry>
<entry key="yaml">
<value>
<list>
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/yaml-3.1.3/lib" />
</list>
</value>
</entry>
</option>
</properties>
<CLASSES>
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.59/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/async-2.13.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/characters-1.4.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/clock-1.1.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/code_assets-1.0.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/collection-1.19.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/crypto-3.0.7/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/csslib-1.0.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cupertino_icons-1.0.8/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio-5.9.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio_web_adapter-2.1.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/fake_async-1.3.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/ffi-2.1.5/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file-7.0.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_auth-5.7.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_auth_platform_interface-7.7.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_auth_web-5.15.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core-3.15.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_platform_interface-6.0.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_web-2.24.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage-9.2.4/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_platform_interface-1.1.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_svg-2.2.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/glob-2.1.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in-6.3.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_android-6.2.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_ios-5.9.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_platform_interface-2.5.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_sign_in_web-0.12.4+4/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/hooks-1.0.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/html-0.15.6/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http-1.6.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.1.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/js-0.6.7/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker-11.0.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-3.0.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.0.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/logging-1.3.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/matcher-0.12.17/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/material_color_utilities-0.11.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/mime-2.0.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/native_toolchain_c-0.17.4/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/objective_c-9.2.3/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path-1.9.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_parsing-1.1.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider-2.1.5/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_android-2.2.22/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_foundation-2.6.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_platform_interface-2.1.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/petitparser-7.0.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/platform-3.1.6/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/pub_semver-2.2.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.12.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stream_channel-2.1.4/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/string_scanner-1.4.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/term_glyph-1.2.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.7.7/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics-1.1.19/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics_codec-1.1.13/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics_compiler-1.1.19/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player-2.10.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_android-2.9.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_avfoundation-2.8.10/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_platform_interface-6.6.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_web-2.4.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vm_service-15.0.2/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/web-1.1.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/win32-5.15.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xdg_directories-1.1.0/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xml-6.6.1/lib" />
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/yaml-3.1.3/lib" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/pkg/sky_engine/lib" />
<root url="file://$PROJECT_DIR$/../../flutter/packages/flutter/lib" />
<root url="file://$PROJECT_DIR$/../../flutter/packages/flutter_test/lib" />
<root url="file://$PROJECT_DIR$/../../flutter/packages/flutter_web_plugins/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

31
.idea/libraries/Dart_SDK.xml generated Normal file
View File

@ -0,0 +1,31 @@
<component name="libraryTable">
<library name="Dart SDK">
<CLASSES>
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/_internal" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/async" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/cli" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/collection" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/concurrent" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/convert" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/core" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/developer" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/ffi" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/html" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/indexed_db" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/io" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/isolate" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js_interop" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js_interop_unsafe" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js_util" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/math" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/mirrors" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/svg" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/typed_data" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/web_audio" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/web_gl" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

35
.idea/rup.iml generated
View File

@ -6,8 +6,43 @@
<excludeFolder url="file://$MODULE_DIR$/app/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/app/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/.pub" /> <excludeFolder url="file://$MODULE_DIR$/app/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/build" /> <excludeFolder url="file://$MODULE_DIR$/app/build" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/build" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/build" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_auth/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_auth/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_auth/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_auth/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_auth/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_auth/example/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_core/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_core/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_core/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_core/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_core/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/firebase_core/example/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/build" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/build" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component> </component>
</module> </module>

View File

@ -41,5 +41,5 @@ migration:
# #
# Files that are not part of the templates will be ignored by default. # Files that are not part of the templates will be ignored by default.
unmanaged_files: unmanaged_files:
- 'lib/main.dart' - "lib/main.dart"
- 'ios/Runner.xcodeproj/project.pbxproj' - "ios/Runner.xcodeproj/project.pbxproj"

Binary file not shown.

View File

@ -0,0 +1,3 @@
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.1 1.9H15.2V0H13.3V1.9H5.7V0H3.8V1.9H1.9C0.85215 1.9 0 2.75215 0 3.8V17.1C0 18.1479 0.85215 19 1.9 19H17.1C18.1479 19 19 18.1479 19 17.1V3.8C19 2.75215 18.1479 1.9 17.1 1.9ZM8.60415 15.5895L5.98595 13.0293L7.31405 11.6707L8.58135 12.9105L11.6745 9.78215L13.0255 11.1179L8.60415 15.5895ZM17.1 7.6H1.89905V3.8H3.8V5.7H5.7V3.8H13.3V5.7H15.2V3.8H17.1V7.6Z" fill="#C8C8C8"/>
</svg>

After

Width:  |  Height:  |  Size: 485 B

View File

@ -0,0 +1,40 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_11594)">
<path d="M9.17107 15.5656C8.97561 15.1182 8.9191 14.6428 8.89732 14.158C8.87503 13.6655 8.99064 13.1901 9.02486 12.7017C9.03782 12.5155 9.07308 12.2485 8.96731 12.0806C8.86258 11.9147 8.67438 11.9178 8.50018 11.9058C7.97498 11.8696 7.45031 11.8338 6.92511 11.7975C6.78616 11.7882 6.68662 11.8872 6.65499 12.0064C6.51293 12.0681 6.40976 12.2335 6.50568 12.4098C6.60003 12.5835 6.56374 12.8209 6.56115 13.0117C6.55856 13.2398 6.55026 13.468 6.54041 13.6961C6.53056 13.9159 6.53264 14.1419 6.50619 14.3602C6.48494 14.5355 6.37347 14.6951 6.47405 14.8662C6.56374 15.0181 6.77164 15.0824 6.92096 15.1566C7.12731 15.2592 7.33573 15.3577 7.54622 15.4526C7.97447 15.6455 8.40945 15.8218 8.85014 15.9835C9.11093 16.0794 9.26076 15.7673 9.1721 15.5646L9.17107 15.5656Z" fill="white"/>
<path d="M16.1529 9.47486C15.8709 9.06579 15.511 8.70235 15.1103 8.41098C14.9164 8.26996 14.7188 8.10509 14.4746 8.07502C14.2201 8.04599 13.9562 8.15383 13.7659 8.32647C13.2112 8.88226 12.5278 9.29029 11.8704 9.71698C11.773 9.7766 11.6734 9.82482 11.5635 9.85229C11.3603 9.90673 11.1337 9.87614 10.9294 9.95339C10.7993 10.0011 10.6801 10.0773 10.5624 10.1525C10.2259 10.3889 9.83082 10.5185 9.47516 10.7223C9.02773 10.9722 8.91419 11.403 8.90952 11.8862C8.8779 12.8443 8.8359 13.7879 8.69436 14.7362C8.67518 14.8704 8.656 15.0089 8.65962 15.1432C8.66066 15.8348 9.4062 15.9691 9.91533 15.9877C10.4784 16.0007 11.0948 16.0111 11.634 15.7845C12.1271 15.5828 12.5792 15.293 13.0427 15.0327C13.2013 14.9394 13.3786 14.8051 13.5679 14.8787C13.6835 14.9166 13.8089 15.0172 13.9572 15.0421C14.2517 15.1001 14.5493 14.8917 14.6115 14.5993C14.6701 14.3743 14.4937 14.1487 14.5398 13.9232C14.5657 13.7858 14.6266 13.5925 14.6872 13.466C14.9838 12.9278 15.3094 12.3829 15.3514 11.7675C15.3742 11.5155 15.354 11.2361 15.5105 11.0256C15.6889 10.8052 15.8854 10.6056 16.0539 10.3713C16.2846 10.0944 16.3717 9.8103 16.156 9.48212L16.1513 9.47434L16.1529 9.47486ZM13.2983 14.6195L13.3024 14.6242C13.3024 14.6242 13.2993 14.6216 13.2983 14.6195Z" fill="#FCF6EB"/>
<path d="M18.9917 6.77611C19.6449 14.3611 10.4371 17.221 9.99228 16.8518C9.72216 17.42 0.582789 14.6816 0.992889 6.77611C1.14894 3.77735 3.42964 1.07671 5.73989 0.899911C7.53168 0.76252 9.11245 1.20943 9.9928 2.7591C10.8726 1.20943 12.4508 0.804515 14.2452 0.899911C16.5777 1.02434 18.734 3.78461 18.9917 6.77611Z" stroke="#C8C8C8" stroke-width="1.83041" stroke-miterlimit="10"/>
<path d="M16.1233 8.1408C15.8599 7.61093 15.7111 7.61352 16.0434 7.03804C16.1404 6.84724 16.2 6.63053 16.2653 6.42937C16.3306 6.25309 16.3021 6.04778 16.0844 6.01253C15.8091 5.97779 15.5058 6.05141 15.287 6.22924C15.0895 6.36352 14.893 6.50247 14.7115 6.66008C14.6083 6.73526 14.4823 6.7394 14.3584 6.72126C13.8089 6.6466 13.2386 6.67252 12.7077 6.78814C12.3971 6.87006 12.3826 6.68082 12.2286 6.44233C12.0238 6.1411 11.6707 5.90883 11.3037 5.89847C10.7334 5.89224 10.7246 6.93227 10.78 7.33304C10.8319 7.64982 10.6634 7.92201 10.5612 8.20975C10.5172 8.3544 10.4928 8.50372 10.4721 8.65459C10.4254 8.9807 10.429 9.31355 10.4918 9.6381C10.5047 9.70394 10.5182 9.76979 10.5192 9.83563C10.5213 9.95799 10.4845 10.0788 10.5488 10.1327C10.6006 10.182 10.7277 10.1794 10.8075 10.1643C10.9501 10.1405 11.0932 10.1104 11.2342 10.0778C12.4521 9.84963 13.1997 9.02995 14.1604 8.35129C14.3854 8.18383 14.5041 8.25848 14.6539 8.4612C14.8328 8.68155 15.0169 8.90137 15.2165 9.10409C15.3767 9.26274 16.1575 10.0264 16.3296 9.70394C16.4841 9.27985 16.3146 8.55038 16.1264 8.14909L16.1227 8.1408H16.1233Z" fill="#D3A061"/>
<path d="M13.9489 9.79043C13.9764 9.85316 14.0106 9.91226 14.051 9.96722C14.0697 9.99262 14.0884 10.017 14.106 10.0434C14.1153 10.0569 14.1236 10.0714 14.1314 10.0859C14.133 10.0896 14.135 10.0927 14.1366 10.0963C14.1376 10.0984 14.1423 10.1103 14.1387 10.0999C14.135 10.0896 14.1397 10.102 14.1402 10.1041C14.1418 10.1088 14.1433 10.1134 14.1449 10.1176C14.1573 10.1559 14.1993 10.1855 14.2408 10.172C14.2792 10.1596 14.3087 10.1176 14.2952 10.0761C14.2729 10.0071 14.232 9.95115 14.1895 9.89308C14.147 9.83501 14.1117 9.77591 14.0837 9.7111C14.0676 9.67377 14.0096 9.66237 13.9774 9.68311C13.9385 9.70799 13.9323 9.74947 13.9494 9.78939L13.9489 9.79043Z" fill="#C8C8C8"/>
<path d="M14.2724 9.32312C14.1884 9.30964 14.0915 9.30497 14.0039 9.30186C13.796 9.29564 14.0474 9.5782 14.1459 9.53828C14.2221 9.52013 14.4446 9.37341 14.2781 9.32415L14.2724 9.32312Z" fill="#C8C8C8"/>
<path d="M13.5394 8.76627C13.5424 8.58161 13.4364 8.43015 13.3027 8.42798C13.169 8.42581 13.0582 8.57375 13.0552 8.75841C13.0522 8.94308 13.1582 9.09453 13.2919 9.0967C13.4256 9.09887 13.5364 8.95093 13.5394 8.76627Z" fill="#C8C8C8"/>
<mask id="mask0_1_11594" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="13" y="8" width="1" height="2">
<path d="M13.5687 8.76725C13.5717 8.58259 13.4657 8.43113 13.332 8.42896C13.1983 8.42679 13.0875 8.57473 13.0845 8.75939C13.0815 8.94405 13.1875 9.09551 13.3212 9.09768C13.4549 9.09985 13.5657 8.95191 13.5687 8.76725Z" fill="white"/>
</mask>
<g mask="url(#mask0_1_11594)">
<path d="M13.3707 8.6624C13.3805 8.66811 13.3909 8.67277 13.4018 8.67537C13.4127 8.67796 13.4251 8.68003 13.437 8.68003C13.4375 8.68003 13.4712 8.67537 13.4718 8.67537C13.4826 8.67277 13.4935 8.66759 13.5034 8.66189C13.5257 8.64996 13.5366 8.63441 13.5511 8.61471C13.5511 8.61471 13.5511 8.61471 13.5511 8.61419C13.5568 8.60434 13.5604 8.59345 13.5646 8.58256C13.5646 8.58256 13.5646 8.58204 13.5646 8.58153C13.5666 8.57012 13.5718 8.51931 13.5692 8.54679C13.5692 8.54679 13.5692 8.54627 13.5692 8.54575C13.5692 8.53383 13.5677 8.5219 13.5646 8.5105C13.5583 8.48924 13.5469 8.46746 13.5303 8.45243C13.5127 8.43584 13.4956 8.42495 13.4723 8.41873C13.4489 8.41251 13.4235 8.41251 13.4002 8.41873C13.3898 8.42288 13.3795 8.42754 13.3691 8.43169C13.3691 8.43169 13.3691 8.43169 13.3686 8.43169C13.3593 8.43791 13.3504 8.44517 13.3416 8.45243C13.3416 8.45243 13.3416 8.45243 13.3411 8.45295C13.3338 8.46176 13.3261 8.47006 13.3204 8.47991C13.3204 8.47991 13.3204 8.47991 13.3204 8.48043C13.3323 8.45502 13.311 8.50116 13.3074 8.51153C13.3074 8.51153 13.3074 8.51205 13.3074 8.51257C13.3017 8.54886 13.3007 8.54679 13.3074 8.58308C13.3074 8.58308 13.3074 8.58308 13.3074 8.5836C13.311 8.59449 13.3147 8.60537 13.3209 8.61522C13.3349 8.63752 13.3458 8.64737 13.3681 8.66189C13.3774 8.66811 13.3883 8.67174 13.3992 8.67485C13.3992 8.67485 13.3992 8.67485 13.3997 8.67485C13.4116 8.67692 13.423 8.67848 13.4349 8.67951H13.436C13.4588 8.67796 13.4832 8.67485 13.5029 8.66137L13.5293 8.64115L13.5495 8.61471C13.5547 8.60486 13.5589 8.59397 13.563 8.58308C13.5682 8.56597 13.5692 8.54886 13.5651 8.53123C13.564 8.51413 13.5594 8.49805 13.5495 8.48302C13.5412 8.46798 13.5303 8.45554 13.5163 8.44673C13.5039 8.43532 13.4894 8.42754 13.4723 8.4234L13.4381 8.41873C13.4147 8.41873 13.393 8.42495 13.3727 8.43636L13.3463 8.45658C13.3302 8.47265 13.3193 8.49183 13.3131 8.51361C13.3173 8.50324 13.3219 8.49287 13.3261 8.48302C13.3261 8.48302 13.3261 8.48302 13.3261 8.48354L13.3463 8.45709L13.3727 8.43687C13.3727 8.43687 13.3727 8.43687 13.3722 8.43687L13.4033 8.42391C13.4033 8.42391 13.4033 8.42391 13.4028 8.42391L13.437 8.41925C13.437 8.41925 13.436 8.41925 13.4355 8.41925L13.4697 8.42391H13.4686L13.4998 8.43687L13.5262 8.45709L13.5464 8.48354L13.5594 8.51464C13.5594 8.51464 13.5594 8.51412 13.5594 8.51361L13.564 8.54782C13.564 8.54782 13.564 8.54679 13.564 8.54627L13.5594 8.58049C13.5594 8.58049 13.5594 8.57945 13.5594 8.57893L13.5464 8.61004C13.5464 8.61004 13.5464 8.609 13.5469 8.609L13.5267 8.63544C13.5267 8.63544 13.5272 8.63493 13.5278 8.63441L13.5013 8.65463C13.5013 8.65463 13.5018 8.65463 13.5023 8.65411L13.4712 8.66707C13.4712 8.66707 13.4723 8.66707 13.4728 8.66707L13.4386 8.67174C13.4386 8.67174 13.4396 8.67174 13.4401 8.67174L13.4059 8.66707C13.4059 8.66707 13.4064 8.66707 13.407 8.66707L13.3758 8.65411C13.3758 8.65411 13.3758 8.65411 13.3764 8.65411L13.3499 8.63389L13.3297 8.60745L13.3167 8.57634C13.3167 8.57634 13.3167 8.57686 13.3167 8.57738L13.3121 8.54316V8.5442L13.3167 8.50998C13.3167 8.50998 13.3167 8.51153 13.3167 8.51205L13.3297 8.48094C13.3297 8.48094 13.3297 8.48146 13.3292 8.48198L13.3494 8.45554C13.3494 8.45554 13.3494 8.45554 13.3489 8.45606L13.3753 8.43584C13.3753 8.43584 13.3753 8.43584 13.3748 8.43584L13.4059 8.42288C13.4059 8.42288 13.4059 8.42288 13.4054 8.42288L13.4396 8.41821C13.4396 8.41821 13.4396 8.41821 13.4391 8.41821L13.4733 8.42288C13.4733 8.42288 13.4733 8.42288 13.4728 8.42288L13.5039 8.43584C13.5039 8.43584 13.5039 8.43584 13.5034 8.43584C13.4884 8.42651 13.4723 8.42132 13.4552 8.42028C13.4381 8.41666 13.421 8.41717 13.4033 8.42236C13.3862 8.42651 13.3717 8.43428 13.3593 8.44569C13.3453 8.45502 13.3338 8.46695 13.3261 8.48198L13.3131 8.51309C13.3069 8.5359 13.3069 8.55923 13.3131 8.58204L13.3261 8.61315C13.3375 8.63285 13.353 8.64789 13.3727 8.65981L13.3707 8.6624Z" fill="white"/>
</g>
<path d="M15.2023 8.81394C15.2256 8.63074 15.137 8.46852 15.0044 8.45162C14.8717 8.43472 14.7453 8.56954 14.7219 8.75275C14.6986 8.93595 14.7872 9.09817 14.9199 9.11507C15.0525 9.13197 15.179 8.99715 15.2023 8.81394Z" fill="#C8C8C8"/>
<mask id="mask1_1_11594" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="14" y="8" width="2" height="2">
<path d="M15.1886 8.80711C15.212 8.6239 15.1234 8.46168 14.9907 8.44479C14.8581 8.42789 14.7316 8.56271 14.7083 8.74591C14.6849 8.92912 14.7735 9.09134 14.9062 9.10823C15.0388 9.12513 15.1653 8.99031 15.1886 8.80711Z" fill="white"/>
</mask>
<g mask="url(#mask1_1_11594)">
<path d="M15.0102 8.689C15.0262 8.70455 15.0465 8.71803 15.0688 8.7227C15.091 8.72736 15.1165 8.72996 15.1387 8.7227C15.1636 8.71492 15.1781 8.70663 15.1973 8.68848C15.2046 8.67967 15.2113 8.67085 15.2175 8.66204C15.2175 8.66204 15.2175 8.66152 15.2181 8.661C15.2222 8.65063 15.2269 8.64026 15.231 8.62989C15.231 8.62989 15.231 8.62938 15.231 8.62886C15.2388 8.59982 15.2362 8.58686 15.231 8.55835C15.2269 8.53554 15.2113 8.51687 15.1973 8.49976C15.1885 8.4925 15.1797 8.48576 15.1709 8.47954C15.1709 8.47954 15.1709 8.47954 15.1704 8.47954L15.1393 8.46658C15.1377 8.46658 15.1066 8.46191 15.1045 8.46191C15.1045 8.46191 15.104 8.46191 15.1035 8.46191C15.0921 8.46347 15.0802 8.46502 15.0688 8.46658C15.0688 8.46658 15.0682 8.46658 15.0677 8.46658C15.0573 8.47125 15.047 8.47539 15.0366 8.47954C15.0366 8.47954 15.0361 8.47954 15.0356 8.47954C15.0262 8.48576 15.0169 8.4925 15.0086 8.50028C15.0086 8.50028 15.0086 8.50028 15.0086 8.5008C15.0014 8.50961 14.9941 8.51791 14.9879 8.52776C14.9879 8.52776 14.9879 8.52776 14.9879 8.52828C14.9832 8.53813 14.9775 8.54901 14.9744 8.5599C14.9744 8.5599 14.9744 8.5599 14.9744 8.56042C14.9811 8.51843 14.9708 8.58375 14.9697 8.59516C14.9697 8.59619 14.9739 8.62834 14.9744 8.62989C14.9775 8.64078 14.9822 8.65167 14.9879 8.66152C14.9988 8.68018 15.0159 8.69781 15.0351 8.7087C15.0449 8.7144 15.0558 8.71855 15.0667 8.72218C15.0667 8.72218 15.0672 8.72218 15.0677 8.72218C15.0791 8.72477 15.091 8.72685 15.103 8.72685C15.103 8.72685 15.103 8.72685 15.1035 8.72685L15.1377 8.72218C15.1486 8.71959 15.1595 8.7144 15.1693 8.7087C15.1792 8.703 15.188 8.69574 15.1963 8.68796C15.2036 8.67915 15.2103 8.67033 15.2165 8.66152C15.2165 8.66152 15.2165 8.66152 15.2165 8.661C15.2258 8.64597 15.231 8.62989 15.2321 8.61279C15.2357 8.59568 15.2352 8.57857 15.23 8.56094C15.2258 8.54383 15.2181 8.52931 15.2067 8.51687C15.1973 8.50287 15.1854 8.49147 15.1704 8.48369L15.1393 8.47073C15.1164 8.46451 15.0931 8.46451 15.0703 8.47073L15.0392 8.48369C15.0195 8.4951 15.0045 8.51065 14.9925 8.53035C14.9925 8.53035 14.9925 8.53035 14.9925 8.53087L15.0128 8.50443L15.0392 8.48421C15.0392 8.48421 15.0392 8.48421 15.0387 8.48421L15.0698 8.47125C15.0698 8.47125 15.0698 8.47125 15.0693 8.47125L15.1035 8.46658C15.1035 8.46658 15.103 8.46658 15.1025 8.46658L15.1367 8.47125C15.1367 8.47125 15.1351 8.47125 15.1346 8.47125L15.1657 8.48421L15.1921 8.50443L15.2124 8.53087L15.2253 8.56198C15.2253 8.56198 15.2253 8.56146 15.2253 8.56094L15.23 8.59516V8.59412L15.2253 8.62834C15.2253 8.62834 15.2253 8.6273 15.2253 8.62678L15.2124 8.65789C15.2124 8.65789 15.2124 8.65685 15.2129 8.65685L15.1927 8.68329C15.1927 8.68329 15.1932 8.68278 15.1937 8.68226L15.1673 8.70248C15.1673 8.70248 15.1678 8.70248 15.1683 8.70196L15.1372 8.71492C15.1372 8.71492 15.1382 8.71492 15.1387 8.71492L15.1045 8.71959C15.1045 8.71959 15.1056 8.71959 15.1061 8.71959L15.0719 8.71492C15.0719 8.71492 15.0724 8.71492 15.0729 8.71492L15.0418 8.70196C15.0418 8.70196 15.0418 8.70196 15.0423 8.70196L15.0159 8.68174L14.9956 8.6553L14.9827 8.62419C14.9827 8.62419 14.9827 8.62419 14.9827 8.62471L14.978 8.59049C14.978 8.59049 14.978 8.59101 14.978 8.59153L14.9827 8.55731C14.9827 8.55731 14.9827 8.55835 14.9827 8.55887L14.9956 8.52776C14.9956 8.52776 14.9957 8.5288 14.9946 8.52931L15.0148 8.50287C15.0148 8.50287 15.0148 8.50339 15.0143 8.50339L15.0408 8.48317C15.0408 8.48317 15.0408 8.48317 15.0402 8.48317L15.0713 8.47021C15.0713 8.47021 15.0713 8.47021 15.0708 8.47021L15.105 8.46554C15.105 8.46554 15.105 8.46554 15.1045 8.46554L15.1387 8.47021C15.1387 8.47021 15.1387 8.47021 15.1382 8.47021L15.1693 8.48317L15.1958 8.50339C15.1838 8.49095 15.1704 8.48162 15.1543 8.47643C15.1387 8.46814 15.1222 8.46451 15.1045 8.46502C15.0869 8.46451 15.0698 8.46814 15.0548 8.47643C15.0387 8.48162 15.0247 8.49043 15.0133 8.50339L14.9931 8.52983C14.9811 8.55005 14.9754 8.57183 14.9754 8.59516L14.9801 8.62938C14.9863 8.65115 14.9972 8.67033 15.0133 8.68641L15.0102 8.689Z" fill="white"/>
</g>
<path d="M14.0282 9.46172C13.9375 9.68155 13.8566 9.90552 13.7861 10.1326C13.7565 10.2285 13.9063 10.2695 13.9359 10.1741C14.0064 9.947 14.0873 9.72303 14.178 9.5032C14.1941 9.46432 14.1609 9.41766 14.1236 9.40729C14.079 9.39484 14.0443 9.42284 14.0277 9.46172H14.0282Z" fill="#C8C8C8"/>
<path d="M14.5301 14.3609C14.5156 14.2546 14.486 14.1541 14.4378 14.0576C14.414 14.0094 14.3855 13.9555 14.3378 13.9275C14.2662 13.885 14.1967 13.9151 14.1273 13.9415C14.0085 13.9866 13.8903 14.0307 13.7721 14.0763C13.6549 14.1214 13.5383 14.1691 13.4325 14.2375C13.3579 14.2858 13.3516 14.3754 13.4061 14.4413C13.4222 14.4605 13.4419 14.4734 13.4626 14.4833C13.545 14.6772 13.6363 14.868 13.8167 14.9898C13.9914 15.1075 14.2341 15.1101 14.3937 14.9644C14.4736 14.8918 14.5094 14.7855 14.5249 14.6813C14.541 14.574 14.5436 14.4688 14.5291 14.3609H14.5301Z" fill="#FCF6EB"/>
<path d="M8.97949 15.4522L8.98831 11.0982C9.44662 10.6876 9.9796 10.3796 10.5903 10.178C10.1756 9.3868 10.3021 8.52979 10.7194 7.63752C10.7194 7.63752 10.4716 5.94009 11.2265 5.80788C11.9814 5.67568 12.4827 6.77792 12.4827 6.77792C13.2651 6.60838 13.98 6.58505 14.6203 6.72296C14.6203 6.72296 15.7967 5.67568 16.2405 5.94061C16.6843 6.20502 15.8553 7.63804 15.8553 7.63804C16.6848 8.96114 16.5324 10.0914 15.4473 11.037C15.4473 11.037 15.399 11.794 15.0351 12.666C14.6711 13.5381 14.498 13.9788 14.498 13.9788C14.498 13.9788 14.8515 14.7922 14.3004 15.0572C13.7493 15.3221 13.3822 14.4609 13.3822 14.4609" stroke="#C8C8C8" stroke-width="0.259229" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.86429 12.3234C8.86429 12.3234 8.10267 16.0195 9.59739 15.9277C11.68 15.8002 10.0142 7.24042 9.29253 7.3016L5.08836 8.59307C4.46569 8.68795 3.70719 15.684 5.682 15.4989C6.98488 15.3766 6.40161 12.2124 6.40161 12.2124" fill="white"/>
<path d="M8.86429 12.3234C8.86429 12.3234 8.10267 16.0195 9.59739 15.9277C11.68 15.8002 10.0142 7.24042 9.29253 7.3016L5.08836 8.59307C4.46569 8.68795 3.70719 15.684 5.682 15.4989C6.98488 15.3766 6.40161 12.2124 6.40161 12.2124" stroke="#C8C8C8" stroke-width="0.259229" stroke-miterlimit="10"/>
<path d="M4.82353 3.92396C4.82353 3.92396 3.85661 1.16473 4.82353 0.752558C5.79045 0.340384 6.26536 2.33852 6.26536 2.33852L8.6653 2.41007C8.6653 2.41007 9.42588 0.235137 10.412 0.604797C11.3981 0.974457 10.2274 4.03802 10.2274 4.03802C10.2274 4.03802 10.3461 9.85253 4.82612 8.62585C4.82612 8.62585 2.25716 8.49624 4.82353 3.92448V3.92396Z" fill="white" stroke="#C8C8C8" stroke-width="0.259229" stroke-miterlimit="10"/>
<path d="M6.19577 4.45593C6.05994 4.45593 6.04231 4.27343 6.14185 4.2247C6.1066 4.19203 6.06616 4.17285 6.02261 4.17285C5.89144 4.17285 5.78516 4.34446 5.78516 4.55651C5.78516 4.76856 5.89144 4.94017 6.02261 4.94017C6.15378 4.94017 6.26006 4.76856 6.26006 4.55651C6.26006 4.51711 6.25643 4.47978 6.24969 4.44401C6.23414 4.45178 6.21651 4.45645 6.19526 4.45645L6.19577 4.45593Z" fill="#C8C8C8"/>
<path d="M7.64109 4.45593C7.50525 4.45593 7.48762 4.27343 7.58717 4.2247C7.55191 4.19203 7.51147 4.17285 7.46792 4.17285C7.33675 4.17285 7.23047 4.34446 7.23047 4.55651C7.23047 4.76856 7.33675 4.94017 7.46792 4.94017C7.59909 4.94017 7.70538 4.76856 7.70538 4.55651C7.70538 4.51711 7.70175 4.47978 7.69501 4.44401C7.67945 4.45178 7.66183 4.45645 7.64057 4.45645L7.64109 4.45593Z" fill="#C8C8C8"/>
<path d="M5.21791 6.60987C5.14689 6.06497 4.68027 5.82078 4.20174 5.67353C3.94769 5.59525 3.79008 5.83218 3.82378 6.0349C3.54848 6.30657 3.58892 6.83436 3.59359 7.17654C3.59618 7.39481 3.60966 7.6079 3.84245 7.69604C4.05501 7.7764 4.35779 7.75359 4.57866 7.70952C5.06549 7.61308 5.27702 7.06196 5.21843 6.60935L5.21791 6.60987Z" fill="#C8C8C8"/>
<path d="M4.70887 8.58105C4.70887 8.58105 4.38328 9.04663 4.90433 9.26594C5.42538 9.48525 5.95317 8.75992 5.95317 8.75992L4.70887 8.58105Z" fill="#E2B4CC" stroke="#C8C8C8" stroke-width="0.259229" stroke-miterlimit="10"/>
<path d="M4.89737 6.24441C4.86989 6.22107 4.83619 6.20552 4.80249 6.19308C4.76879 6.18063 4.73302 6.17441 4.69724 6.17182C4.66821 6.16975 4.63762 6.17493 4.61377 6.191C4.59874 6.19619 4.58526 6.20552 4.57489 6.22056C4.55622 6.24803 4.55622 6.2864 4.5723 6.31492C4.58889 6.34395 4.61792 6.36158 4.64747 6.37454C4.67599 6.3875 4.70554 6.39839 4.73613 6.40565C4.7605 6.41135 4.78849 6.41498 4.81597 6.41394C4.82686 6.41446 4.83723 6.4129 4.84656 6.40979C4.87819 6.40305 4.90618 6.3875 4.92277 6.35639C4.94351 6.31803 4.93055 6.27136 4.89789 6.24389L4.89737 6.24441Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1_11594">
<rect width="20" height="17.8173" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.7 9.3L10.7 0.3C10.3 -0.1 9.7 -0.1 9.3 0.3L0.3 9.3C-0.1 9.7 -0.1 10.3 0.3 10.7C0.7 11.1 1.3 11.1 1.7 10.7L2 10.4V18C2 19.1 2.9 20 4 20H7V14C7 12.3 8.3 11 10 11C11.7 11 13 12.3 13 14V20H16C17.1 20 18 19.1 18 18V10.4L18.3 10.7C18.5 10.9 18.8 11 19 11C19.2 11 19.5 11 19.7 10.7C20.1 10.3 20.1 9.7 19.7 9.3Z" fill="#C8C8C8"/>
</svg>

After

Width:  |  Height:  |  Size: 437 B

View File

@ -0,0 +1,4 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 10C10.4 10 11.7 9.4 12.5 8.5C13.5 7.5 14 6.3 14 5C14 3.7 13.4 2.3 12.5 1.5C11.5 0.5 10.3 0 9 0C7.7 0 6.3 0.6 5.5 1.5C4.5 2.5 4 3.7 4 5C4 6.3 4.5 7.7 5.5 8.5C6.5 9.5 7.7 10 9 10Z" fill="#C8C8C8"/>
<path d="M16.6 13.5C15.7 12.5 14.5 12 13 12H5C3.7 12 2.5 12.5 1.5 13.4C0.5 14.3 0 15.5 0 17V20H18V17C18 15.7 17.5 14.5 16.6 13.5Z" fill="#C8C8C8"/>
</svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@ -0,0 +1,5 @@
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 2.75C0 1.23579 1.23579 0 2.75 0H16.75C18.2642 0 19.5 1.23579 19.5 2.75V5.35C19.5 6.06952 19.3201 6.79295 18.8708 7.38059C18.4119 7.98071 17.7255 8.36826 16.8561 8.49246C15.7712 8.64745 14.8274 8.22121 14.2194 7.5064C14.1317 7.6035 14.0388 7.69451 13.9418 7.77877C13.3344 8.30621 12.491 8.63385 11.6267 8.4898C10.862 8.36235 10.2221 7.98527 9.76535 7.45444C9.66441 7.57118 9.55592 7.67964 9.44176 7.77877C8.83442 8.30621 7.99101 8.63385 7.1267 8.4898C6.36202 8.36235 5.72209 7.98527 5.26535 7.45444C5.16441 7.57118 5.05592 7.67964 4.94176 7.77877C4.33442 8.30621 3.49101 8.63385 2.6267 8.4898C1.04724 8.22655 0 6.89826 0 5.35V2.75Z" fill="#C8C8C8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 17V7.65002H1V18.5H18.5V7.65002H17V17H2.5Z" fill="#C8C8C8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.75 11.5C11.0642 11.5 10.5 12.0642 10.5 12.75V14H13V12.75C13 12.0642 12.4358 11.5 11.75 11.5ZM9 12.75C9 11.2358 10.2358 10 11.75 10C13.2642 10 14.5 11.2358 14.5 12.75V15.5H9V12.75Z" fill="#C8C8C8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

View File

@ -0,0 +1,34 @@
class TermsData {
static const List<Map<String, String>> terms = [
{
'title': '[제1조 목적]',
'content':
'본 약관은 RUP(이하 "회사")가 제공하는 서비스 이용과 관련하여 회사와 회원의 권리, 의무 및 책임사항을 규정함을 목적으로 합니다.\\n\\n[제2조 정의]\\n1. "서비스"란 구현되는 단말기와 상관없이 회원이 이용할 수 있는 RUP 및 관련 제반 서비스를 의미합니다.\\n2. "회원"이란 회사의 서비스에 접속하여 본 약관에 따라 회사와 이용계약을 체결하고 회사가 제공하는 서비스를 이용하는 고객을 말합니다.',
},
{
'title': '[개인정보 수집 및 이용 동의]',
'content':
'회사는 회원가입, 고객상담, 서비스 신청 등을 위해 아래와 같은 개인정보를 수집하고 있습니다.\\n\\n1. 수집 항목\\n- 필수항목: 이메일, 닉네임, 비밀번호, 서비스 이용 기록\\n- 선택항목: 프로필 사진, 위치 정보\\n\\n2. 수집 목적\\n- 서비스 제공, 회원 관리, 신규 서비스 개발 및 마케팅 광고에의 활용',
},
{
'title': '[개인정보 제3자 제공 동의]',
'content':
'회사는 이용자의 개인정보를 원칙적으로 외부에 제공하지 않습니다. 다만, 아래의 경우에는 예외로 합니다.\\n\\n1. 이용자들이 사전에 동의한 경우\\n2. 법령의 규정에 의거하거나, 수사 목적으로 법령에 정해진 절차와 방법에 따라 수사기관의 요구가 있는 경우',
},
{
'title': '[만 14세 이상 이용 동의]',
'content':
'본 서비스는 만 14세 이상만 이용 가능합니다.\\n만 14세 미만 아동의 경우 회원가입 및 서비스 이용이 제한될 수 있습니다.',
},
{
'title': '[위치기반 서비스 이용약관]',
'content':
'본 약관은 회사가 제공하는 위치기반서비스와 관련하여 회사와 개인위치정보주체와의 권리, 의무 및 책임사항, 기타 필요한 사항을 규정함을 목적으로 합니다.\\n\\n회사는 이용자의 위치 정보를 이용하여 주변 펫 프렌들리 장소 추천 등의 서비스를 제공합니다.',
},
{
'title': '[마케팅 정보 수신 동의]',
'content':
'회사가 제공하는 이벤트, 혜택, 뉴스레터 등 다양한 마케팅 정보를 이메일, 앱 푸시 알림 등으로 받아보실 수 있습니다.\\n\\n동의를 거부하시더라도 기본 서비스 이용에는 제한이 없으나, 이벤트 참여 및 혜택 제공이 제한될 수 있습니다.',
},
];
}

View File

@ -1,16 +1,33 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'screens/splash_screen.dart';
import 'dart:developer'; import 'dart:developer';
import 'screens/splash_screen.dart';
import 'utils/log_manager.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
//
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
LogManager().addLog('[APP ERROR] ${details.exception}');
};
PlatformDispatcher.instance.onError = (error, stack) {
LogManager().addLog('[Uncaught Error] $error');
return true;
};
try { try {
await Firebase.initializeApp(); await Firebase.initializeApp();
} catch (e) { } catch (e) {
log('Firebase initialization failed: $e'); log('Firebase initialization failed: $e');
LogManager().addLog('[Firebase Init Error] $e');
} }
runApp(RupApp()); runApp(const RupApp());
} }
class RupApp extends StatelessWidget { class RupApp extends StatelessWidget {
@ -18,6 +35,10 @@ class RupApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp(debugShowCheckedModeBanner: false, home: SplashScreen()); return MaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
home: const SplashScreen(),
);
} }
} }

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'welcome_screen.dart';
class HomeScreen extends StatelessWidget { class HomeScreen extends StatelessWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@ -7,26 +6,8 @@ class HomeScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( body: const SafeArea(
title: const Text( child: Center(
'',
style: TextStyle(fontFamily: 'SCDream', fontWeight: FontWeight.bold),
),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () {
// Mock Logout: Go back to Welcome Screen
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const WelcomeScreen()),
);
},
),
],
),
body: const Center(
child: Text( child: Text(
'로그인 성공!\n여기는 메인 홈 화면입니다.', '로그인 성공!\n여기는 메인 홈 화면입니다.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -37,6 +18,7 @@ class HomeScreen extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import 'main_screen.dart';
class IdentityVerificationScreen extends StatelessWidget {
const IdentityVerificationScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black, size: 20),
onPressed: () => Navigator.pop(context),
),
title: const Text(
'본인 인증',
style: TextStyle(
fontSize: 15,
fontFamily: 'SCDream',
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
const Text(
'더 안전한 서비스 이용을 위해\n본인 인증을 진행해 주세요.',
style: TextStyle(
fontSize: 20,
fontFamily: 'SCDream',
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 40),
// UI (Placeholder)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey[300]!),
),
child: Column(
children: [
const Icon(
Icons.shield_outlined,
size: 50,
color: Color(0xFFFF7500),
),
const SizedBox(height: 10),
const Text(
'PASS / 문자 인증',
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 10),
const Text(
'(현재 UI만 구현된 상태입니다)',
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
const Spacer(),
//
TextButton(
onPressed: () {
// ( )
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const MainScreen()),
(route) => false,
);
},
child: const Text(
'다음에 하기 (건너뛰기)',
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey,
decoration: TextDecoration.underline,
),
),
),
const SizedBox(height: 10),
// ( X)
SizedBox(
height: 50,
child: ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('본인 인증 기능은 추후 구현 예정입니다.')),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF7500),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'인증하기',
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 10),
],
),
),
);
}
}

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import 'home_screen.dart'; import 'main_screen.dart';
import 'terms_agreement_screen.dart'; // Import TermsAgreementScreen
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
const LoginScreen({super.key}); const LoginScreen({super.key});
@ -20,15 +21,27 @@ class _LoginScreenState extends State<LoginScreen> {
try { try {
final authService = AuthService(); final authService = AuthService();
final user = await authService.signInWithGoogle(); final result = await authService.signInWithGoogle();
if (!mounted) return; if (!mounted) return;
if (user != null) { if (result != null) {
final isNewUser = result['isNewUser'] as bool;
if (isNewUser) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
TermsAgreementScreen(idToken: result['idToken']),
),
);
} else {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const HomeScreen()), MaterialPageRoute(builder: (context) => const MainScreen()),
); );
}
} else { } else {
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,

View File

@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'home_screen.dart';
import 'reservation_screen.dart';
import 'mungnyangz_screen.dart';
import 'shop_screen.dart';
import 'my_info_screen.dart';
import '../theme/app_colors.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedIndex = 0;
//
final List<Widget> _screens = [
const HomeScreen(),
const ReservationScreen(),
const MungNyangzScreen(),
const ShopScreen(),
const MyInfoScreen(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
// SVG ( )
Widget _buildSvgIcon(String assetName, int index) {
return SvgPicture.asset(
assetName,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
_selectedIndex == index
? AppColors.highlight
: AppColors.inactive, // : , :
BlendMode.srcIn,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(index: _selectedIndex, children: _screens),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped,
type: BottomNavigationBarType.fixed,
selectedItemColor: AppColors.highlight,
unselectedItemColor: AppColors.inactive,
selectedLabelStyle: TextStyle(
fontFamily: 'SCDream',
fontSize: 12,
fontWeight: FontWeight.w500, // Medium
),
unselectedLabelStyle: TextStyle(
fontFamily: 'SCDream',
fontSize: 12,
fontWeight: FontWeight.w400, // Regular
),
showUnselectedLabels: true,
items: [
BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: _buildSvgIcon('assets/icons/homeicon.svg', 0),
),
label: '',
),
BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: _buildSvgIcon('assets/icons/appointmenticon.svg', 1),
),
label: '예약/조회',
),
BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Image.asset(
_selectedIndex == 2
? 'assets/img/catdog_on.png'
: 'assets/img/catdog_off.png',
width: 24,
height: 24,
),
),
label: '멍냥즈',
),
BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: _buildSvgIcon('assets/icons/shopicon.svg', 3),
),
label: '상점',
),
BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: _buildSvgIcon('assets/icons/myicon.svg', 4),
),
label: '내 정보',
),
],
),
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import '../utils/log_manager.dart';
class MungNyangzScreen extends StatelessWidget {
const MungNyangzScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ValueListenableBuilder<List<String>>(
valueListenable: LogManager().logs,
builder: (context, logs, child) {
if (logs.isEmpty) {
return const Center(
child: Text('로그가 없습니다.', style: TextStyle(color: Colors.grey)),
);
}
return ListView.builder(
padding: const EdgeInsets.all(10),
itemCount: logs.length,
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.only(bottom: 5),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(5),
),
child: Text(
logs[index],
style: const TextStyle(fontSize: 12, fontFamily: 'SCDream'),
),
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
LogManager().clear();
},
mini: true,
child: const Icon(Icons.delete),
),
);
}
}

View File

@ -0,0 +1,398 @@
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
import 'welcome_screen.dart';
import 'notice_screen.dart'; //
import '../data/terms_data.dart'; //
class MyInfoScreen extends StatefulWidget {
const MyInfoScreen({super.key});
@override
State<MyInfoScreen> createState() => _MyInfoScreenState();
}
class _MyInfoScreenState extends State<MyInfoScreen> {
final AuthService _authService = AuthService();
Map<String, dynamic>? _userInfo;
bool _isLoading = true;
@override
void initState() {
super.initState();
_fetchUserInfo();
}
Future<void> _fetchUserInfo() async {
try {
final info = await _authService.getUserInfo();
if (mounted) {
setState(() {
_userInfo = info;
});
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('정보를 불러올 수 없습니다. 잠시 후 다시 시도해주세요.')),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _handleLogout() async {
await _authService.signOut();
if (mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const WelcomeScreen()),
(route) => false,
);
}
}
Future<void> _handleWithdraw() async {
bool? confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('회원 탈퇴'),
content: const Text('정말로 탈퇴하시겠습니까?\n모든 데이터가 삭제되며 복구할 수 없습니다.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('취소'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('탈퇴하기', style: TextStyle(color: Colors.red)),
),
],
),
);
if (confirm == true) {
if (!mounted) return;
final success = await _authService.withdrawAccount();
if (success && mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const WelcomeScreen()),
(route) => false,
);
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('탈퇴 처리에 실패했습니다. 다시 시도해주세요.')),
);
}
}
}
}
//
void _showAllTermsModal(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent, //
builder: (context) {
return DraggableScrollableSheet(
initialChildSize: 0.85, // 85%
minChildSize: 0.5,
maxChildSize: 0.95,
builder: (_, controller) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Center(
child: Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 20, top: 10),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
),
//
const Row(
children: [
Icon(Icons.description, size: 24, color: Colors.black87),
SizedBox(width: 8),
Text(
'서비스 이용 약관 전체보기',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
),
),
],
),
const SizedBox(height: 20),
// ( )
Expanded(
child: ListView.separated(
controller: controller, //
itemCount: TermsData.terms.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Divider(color: Color(0xFFEEEEEE), thickness: 1),
),
itemBuilder: (context, index) {
final term = TermsData.terms[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
term['title']!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFFFF7500), //
fontFamily: 'SCDream',
),
),
const SizedBox(height: 10),
Text(
term['content']!,
style: const TextStyle(
fontSize: 14,
height: 1.6,
color: Colors.black87,
fontFamily: 'SCDream',
),
),
],
);
},
),
),
const SizedBox(height: 10),
//
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF7500),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 0,
),
child: const Text(
'닫기',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
),
),
),
),
],
),
);
},
);
},
);
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return Scaffold(
backgroundColor: Colors.white,
body: _userInfo == null
? const Center(child: Text('정보를 불러올 수 없습니다.'))
: SafeArea(
child: Column(
// Column으로
children: [
Expanded(
//
child: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
const CircleAvatar(
radius: 40,
backgroundColor: Colors.grey,
child: Icon(
Icons.person,
size: 50,
color: Colors.white,
),
),
const SizedBox(height: 16),
Text(
_userInfo!['nickname'] ?? '이름 없음',
style: const TextStyle(
fontFamily: 'SCDream',
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
_userInfo!['email'] ?? '이메일 없음',
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
const SizedBox(height: 30),
_buildMenuItem(
title: '공지사항',
icon: Icons.campaign_outlined,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NoticeScreen(),
),
);
},
),
const SizedBox(height: 10),
_buildMenuItem(
title: '서비스 이용 약관',
icon: Icons.description_outlined,
onTap: () => _showAllTermsModal(context),
),
const SizedBox(height: 10),
_buildMenuItem(
title: '버전 정보',
icon: Icons.info_outline,
trailingText: '1.0.0',
onTap: () {}, //
),
// removed from here
],
),
),
),
// ( & )
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 30),
child: Column(
children: [
// ( )
_buildMenuItem(
title: '로그아웃',
icon: Icons.logout,
onTap: _handleLogout,
),
const SizedBox(height: 10),
//
_buildMenuItem(
title: '회원 탈퇴',
icon: Icons.person_off_outlined,
isDestructive: true,
onTap: _handleWithdraw,
),
],
),
),
],
),
),
);
}
Widget _buildMenuItem({
required String title,
required IconData icon,
VoidCallback? onTap, // onTap을 nullable로
bool isDestructive = false,
String? trailingText, //
}) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey[200]!),
borderRadius: BorderRadius.circular(12),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
child: Row(
children: [
Icon(icon, color: isDestructive ? Colors.red : Colors.black54),
const SizedBox(width: 16),
Expanded(
child: Text(
title,
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 16,
fontWeight: FontWeight.w500, // Medium
color: isDestructive ? Colors.red : Colors.black,
),
),
),
if (trailingText != null)
Text(
trailingText,
style: const TextStyle(
fontFamily: 'SCDream',
fontSize: 14,
color: Colors.grey,
fontWeight: FontWeight.bold,
),
)
else
Icon(
Icons.arrow_forward_ios,
size: 16,
color: isDestructive ? Colors.red : Colors.grey,
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
class NoticeScreen extends StatelessWidget {
const NoticeScreen({super.key});
final List<Map<String, String>> notices = const [
{
'title': 'RUP 서비스 런칭 안내',
'date': '2024.01.20',
'content': '반려동물 통합 관리 플랫폼 RUP가 정식 런칭되었습니다. 많은 이용 부탁드립니다.',
},
{
'title': '시스템 점검 안내',
'date': '2024.01.15',
'content': '더나은 서비스를 위해 시스템 점검이 진행될 예정입니다.\n일시: 2024.01.25 02:00 ~ 04:00',
},
{
'title': '이용약관 개정 안내',
'date': '2024.01.10',
'content': '서비스 이용약관이 일부 개정되었습니다. 주요 변경사항을 확인해주세요.',
},
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text(
'공지사항',
style: TextStyle(
fontFamily: 'SCDream',
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black, size: 20),
onPressed: () => Navigator.pop(context),
),
),
body: ListView.separated(
itemCount: notices.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Color(0xFFEEEEEE)),
itemBuilder: (context, index) {
final notice = notices[index];
return ExpansionTile(
tilePadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 8,
),
title: Text(
notice['title']!,
style: const TextStyle(
fontFamily: 'SCDream',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
notice['date']!,
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 12,
color: Colors.grey[500],
),
),
),
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 20,
),
color: Colors.grey[50],
child: Text(
notice['content']!,
style: const TextStyle(
fontFamily: 'SCDream',
fontSize: 14,
height: 1.5,
color: Colors.black87,
),
),
),
],
);
},
),
);
}
}

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class ReservationScreen extends StatelessWidget {
const ReservationScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: const SafeArea(
child: Center(
child: Text(
'예약 조회 화면 준비 중입니다.',
style: TextStyle(fontFamily: 'SCDream'),
),
),
),
);
}
}

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class ShopScreen extends StatelessWidget {
const ShopScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: const SafeArea(
child: Center(
child: Text(
'상점 화면 준비 중입니다.',
style: TextStyle(fontFamily: 'SCDream'),
),
),
),
);
}
}

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import 'home_screen.dart'; import 'main_screen.dart';
import 'terms_agreement_screen.dart';
class SignupScreen extends StatefulWidget { class SignupScreen extends StatefulWidget {
const SignupScreen({super.key}); const SignupScreen({super.key});
@ -20,15 +21,30 @@ class _SignupScreenState extends State<SignupScreen> {
try { try {
final authService = AuthService(); final authService = AuthService();
final user = await authService.signInWithGoogle(); // Returns Map<String, dynamic>? { 'credential': ..., 'isNewUser': bool }
final result = await authService.signInWithGoogle();
if (!mounted) return; if (!mounted) return;
if (user != null) { if (result != null) {
final isNewUser = result['isNewUser'] as bool;
if (isNewUser) {
// ->
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
TermsAgreementScreen(idToken: result['idToken']),
),
);
} else {
// ->
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const HomeScreen()), MaterialPageRoute(builder: (context) => const MainScreen()),
); );
}
} else { } else {
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
@ -88,7 +104,7 @@ class _SignupScreenState extends State<SignupScreen> {
Image.asset('assets/img/foot.png', width: 30), Image.asset('assets/img/foot.png', width: 30),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text( const Text(
'서비스 이용 약관', '간편 로그인',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontFamily: 'SCDream', fontFamily: 'SCDream',

View File

@ -1,8 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// import 'package:video_player/video_player.dart';
import 'welcome_screen.dart'; import 'welcome_screen.dart';
import 'home_screen.dart'; import 'main_screen.dart';
import '../services/auth_service.dart'; // Import AuthService
import '../theme/app_colors.dart';
class SplashScreen extends StatefulWidget { class SplashScreen extends StatefulWidget {
const SplashScreen({super.key}); const SplashScreen({super.key});
@ -12,9 +13,6 @@ class SplashScreen extends StatefulWidget {
} }
class _SplashScreenState extends State<SplashScreen> { class _SplashScreenState extends State<SplashScreen> {
// Mock login history flag
final bool hasLoginHistory = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -22,17 +20,22 @@ class _SplashScreenState extends State<SplashScreen> {
} }
Future<void> _checkLoginHistory() async { Future<void> _checkLoginHistory() async {
// Simulate loading time (e.g. 2 seconds) // Simulate loading time (minimum)
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
if (!mounted) return; if (!mounted) return;
if (hasLoginHistory) { final AuthService authService = AuthService();
final String? token = await authService.getAccessToken();
if (token != null) {
// ( )
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const HomeScreen()), MaterialPageRoute(builder: (context) => const MainScreen()),
); );
} else { } else {
//
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const WelcomeScreen()), MaterialPageRoute(builder: (context) => const WelcomeScreen()),
@ -43,7 +46,7 @@ class _SplashScreenState extends State<SplashScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFFF7500), backgroundColor: AppColors.highlight,
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View File

@ -0,0 +1,404 @@
import 'package:flutter/material.dart';
import 'identity_verification_screen.dart';
import '../services/auth_service.dart'; // Import AuthService
import '../data/terms_data.dart';
class TermsAgreementScreen extends StatefulWidget {
final bool isViewOnly;
final String? idToken;
const TermsAgreementScreen({
super.key,
this.isViewOnly = false,
this.idToken,
});
@override
State<TermsAgreementScreen> createState() => _TermsAgreementScreenState();
}
class _TermsAgreementScreenState extends State<TermsAgreementScreen> {
final List<bool> _checks = [false, false, false, false, false, false];
bool get _isAllChecked => _checks.every((completed) => completed);
bool get _isRequiredChecked =>
_checks[0] && _checks[1] && _checks[2] && _checks[3];
void _toggleAll() {
setState(() {
bool newValue = !_isAllChecked;
for (int i = 0; i < _checks.length; i++) {
_checks[i] = newValue;
}
});
}
void _toggleItem(int index) {
setState(() {
_checks[index] = !_checks[index];
});
}
String _getTermContent(int index) {
if (index >= 0 && index < TermsData.terms.length) {
return TermsData.terms[index]['content'] ?? '내용이 없습니다.';
}
return '내용이 없습니다.';
}
void _showTermDetail(BuildContext context, String title, int index) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return DraggableScrollableSheet(
initialChildSize: 0.7,
minChildSize: 0.5,
maxChildSize: 0.9,
builder: (_, controller) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 20),
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
),
),
const SizedBox(height: 20),
Expanded(
child: SingleChildScrollView(
controller: controller,
child: Text(
_getTermContent(index),
style: const TextStyle(
fontSize: 15,
height: 1.6,
fontFamily: 'SCDream',
color: Colors.black87,
),
),
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF7500),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 0,
),
child: const Text(
'확인',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
),
),
),
),
],
),
);
},
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black, size: 20),
onPressed: () => Navigator.pop(context),
),
title: Text(
widget.isViewOnly ? '이용 약관' : '회원가입',
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'SCDream',
),
),
centerTitle: true,
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
// Header Area
Row(
children: [
Image.asset(
'assets/img/foot.png',
width: 24,
height: 24,
),
const SizedBox(width: 8),
const Text(
'서비스 이용 약관',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
color: Colors.black,
),
),
],
),
const SizedBox(height: 8),
Text(
widget.isViewOnly
? 'RUP 서비스의 이용 약관 내용입니다.'
: '서비스 이용을 위해 약관에 동의해주세요.',
style: const TextStyle(
fontSize: 15,
color: Colors.black54,
fontFamily: 'SCDream',
),
),
const SizedBox(height: 30),
// All Agree Box - *ViewOnly *
if (!widget.isViewOnly) ...[
InkWell(
onTap: () => _toggleAll(),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
_isAllChecked
? Icons.radio_button_checked
: Icons.radio_button_off,
color: _isAllChecked
? const Color(0xFFFF7500)
: Colors.grey,
),
const SizedBox(width: 12),
const Text(
'모든 약관에 동의합니다.',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
),
),
],
),
),
),
const SizedBox(height: 20),
const Divider(height: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 10),
],
// Individual Items
_buildTermItem(0, '이용약관 동의', isRequired: true),
_buildTermItem(1, '개인정보 수집 및 이용 동의', isRequired: true),
_buildTermItem(2, '제 3자 제공 동의', isRequired: true),
_buildTermItem(3, '만 14세 이상 사용자', isRequired: true),
_buildTermItem(4, '위치정보 이용 동의', isRequired: false),
_buildTermItem(5, '마케팅 수신 동의', isRequired: false),
],
),
),
),
// Bottom Button
Padding(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: widget.isViewOnly
? () => Navigator.pop(context)
: (_isRequiredChecked
? () async {
if (widget.idToken != null) {
// (DB )
final success = await AuthService()
.registerWithGoogle(widget.idToken!);
if (success && context.mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const IdentityVerificationScreen(),
),
);
} else if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('회원가입 처리에 실패했습니다.'),
),
);
}
} else {
// idToken이 ( ) -
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const IdentityVerificationScreen(),
),
);
}
}
: null),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF7500),
disabledBackgroundColor: Colors.grey[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 0,
),
child: Text(
widget.isViewOnly ? '닫기' : '동의하고 본인 인증하기',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: 'SCDream',
),
),
),
),
),
],
),
),
);
}
Widget _buildTermItem(int index, String title, {required bool isRequired}) {
// ViewOnly
// ViewOnly ( X)
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Expanded(
child: InkWell(
onTap: () {
if (!widget.isViewOnly) {
_toggleItem(index);
} else {
// ViewOnly일 ()
_showTermDetail(context, title, index);
}
},
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 8,
),
child: Row(
children: [
// Icon - *ViewOnly *
if (!widget.isViewOnly) ...[
Icon(
Icons.check,
size: 20,
color: _checks[index]
? const Color(0xFFFF7500)
: Colors.grey[300],
),
const SizedBox(width: 12),
],
Text(
isRequired ? '필수' : '선택',
style: TextStyle(
color: isRequired
? const Color(0xFFFF7500)
: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 14,
fontFamily: 'SCDream',
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: const TextStyle(
fontSize: 15,
color: Colors.black87,
fontFamily: 'SCDream',
),
),
),
],
),
),
),
),
IconButton(
onPressed: () => _showTermDetail(context, title, index),
icon: const Icon(
Icons.arrow_forward_ios,
size: 14,
color: Colors.black,
),
splashRadius: 20,
padding: const EdgeInsets.all(12),
constraints: const BoxConstraints(),
),
],
),
);
}
}

View File

@ -1,51 +1,256 @@
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart'; import 'package:google_sign_in/google_sign_in.dart';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter/material.dart';
import '../main.dart';
import '../screens/welcome_screen.dart';
import '../utils/log_manager.dart';
class AuthService { class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance; final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn(); // Google Sign-In (Backend와 serverClientId )
final GoogleSignIn _googleSignIn = GoogleSignIn(
serverClientId:
'379988243470-g6490l8gucc3ljras93i28c3l4qlroi4.apps.googleusercontent.com',
);
final Dio _dio = Dio();
final FlutterSecureStorage _storage = const FlutterSecureStorage();
// // Backend Base URL
Future<UserCredential?> signInWithGoogle() async { final String _baseUrl = 'http://10.0.2.2:3000/auth';
// 1.
print('[DEBUG] Google Sign-In: Starting signIn()'); AuthService() {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); // Dio Options (Timeout )
print( _dio.options.connectTimeout = const Duration(seconds: 5); // 5
'[DEBUG] Google Sign-In: signIn() completed, user: ${googleUser?.email}', _dio.options.receiveTimeout = const Duration(seconds: 5); // 5
// Dio Interceptor
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
// Access Token
final accessToken = await _storage.read(key: 'accessToken');
if (accessToken != null) {
options.headers['Authorization'] = 'Bearer $accessToken';
}
return handler.next(options);
},
onError: (DioException error, handler) async {
// 401 (Access Token )
if (error.response?.statusCode == 401) {
String msg1 = '[Auth] Access Token expired. Attempting refresh...';
print(msg1);
LogManager().addLog(msg1); // LOG
final isRefreshed = await _refreshToken();
if (isRefreshed) {
// ->
final newAccessToken = await _storage.read(key: 'accessToken');
//
error.requestOptions.headers['Authorization'] =
'Bearer $newAccessToken';
//
try {
final response = await _dio.fetch(error.requestOptions);
return handler.resolve(response);
} catch (e) {
return handler.reject(error);
}
} else {
// ( ) -> &
String msg2 = '[Auth] Refresh Token expired. Logging out...';
print(msg2);
LogManager().addLog(msg2); // LOG
await signOut();
// Force Navigation to Welcome Screen
navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const WelcomeScreen()),
(route) => false,
);
}
} else {
// Log other errors
LogManager().addLog(
'[DioError] ${error.message} (Status: ${error.response?.statusCode})',
);
}
return handler.next(error);
},
),
);
}
//
Future<bool> _refreshToken() async {
try {
final refreshToken = await _storage.read(key: 'refreshToken');
if (refreshToken == null) return false;
final response = await _dio.post(
'$_baseUrl/refresh',
data: {'refreshToken': refreshToken},
); );
if (response.statusCode == 200 && response.data['success'] == true) {
final newAccessToken = response.data['accessToken'];
await _storage.write(key: 'accessToken', value: newAccessToken);
String msg = '[Auth] Token refreshed successfully.';
print(msg);
LogManager().addLog(msg);
return true;
}
return false;
} catch (e) {
String msg = '[Auth] Token refresh failed: $e';
print(msg);
LogManager().addLog(msg);
return false;
}
}
// (Check or Login)
Future<Map<String, dynamic>?> signInWithGoogle() async {
try {
LogManager().addLog('[DEBUG] Google Sign-In: Starting signIn()');
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) { if (googleUser == null) {
// LogManager().addLog('[DEBUG] Google Sign-In: User canceled');
print('[DEBUG] Google Sign-In: User canceled');
return null; return null;
} }
// 2.
print('[DEBUG] Google Sign-In: Getting authentication...');
final GoogleSignInAuthentication googleAuth = final GoogleSignInAuthentication googleAuth =
await googleUser.authentication; await googleUser.authentication;
print(
'[DEBUG] Google Sign-In: Authentication received. AccessToken: ${googleAuth.accessToken != null}, IDToken: ${googleAuth.idToken != null}', if (googleAuth.idToken != null) {
try {
// 1. (Login Check)
final response = await _dio.post(
'$_baseUrl/google',
data: {'idToken': googleAuth.idToken},
); );
// 3. if (response.statusCode == 200 && response.data['success'] == true) {
final isNewUser = response.data['isNewUser'] ?? false;
if (isNewUser) {
// : idToken ( )
return {
'isNewUser': true,
'idToken': googleAuth.idToken,
'email': response.data['email'],
'nickname': response.data['nickname'],
};
} else {
// :
final accessToken = response.data['accessToken'];
final refreshToken = response.data['refreshToken'];
await _storage.write(key: 'accessToken', value: accessToken);
await _storage.write(key: 'refreshToken', value: refreshToken);
// Firebase ( , )
final OAuthCredential credential = GoogleAuthProvider.credential( final OAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken, accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken, idToken: googleAuth.idToken,
); );
await _auth.signInWithCredential(credential);
// 4. Firebase에 return {'isNewUser': false};
print('[DEBUG] Google Sign-In: Signing in with credential to Firebase...'); }
final userCredential = await _auth.signInWithCredential(credential); }
print( } catch (e) {
'[DEBUG] Google Sign-In: Firebase sign-in completed. User: ${userCredential.user?.uid}', String msg = '[ERROR] Backend Auth API Error: $e';
print(msg);
LogManager().addLog(msg);
}
}
return null;
} catch (e) {
String msg = '[ERROR] Google Sign-In Error: $e';
print(msg);
LogManager().addLog(msg);
return null;
}
}
// (Register - )
Future<bool> registerWithGoogle(String idToken) async {
try {
final response = await _dio.post(
'$_baseUrl/google/register',
data: {'idToken': idToken},
); );
return userCredential;
if (response.statusCode == 200 && response.data['success'] == true) {
final accessToken = response.data['accessToken'];
final refreshToken = response.data['refreshToken'];
await _storage.write(key: 'accessToken', value: accessToken);
await _storage.write(key: 'refreshToken', value: refreshToken);
// Firebase () - idToken으로 credential access token이
// signInWithGoogle에서 credential을 .
// .
// (Firebase Auth와 Custom Backend Auth를 .
// )
return true;
}
return false;
} catch (e) {
LogManager().addLog('[Auth] Register Failed: $e');
return false;
}
} }
// //
Future<void> signOut() async { Future<void> signOut() async {
await _googleSignIn.signOut(); await _googleSignIn.signOut();
await _auth.signOut(); await _auth.signOut();
await _storage.deleteAll(); //
print('[DEBUG] User signed out and tokens cleared.');
} }
// Access Token
Future<String?> getAccessToken() async {
return await _storage.read(key: 'accessToken');
}
//
Future<Map<String, dynamic>?> getUserInfo() async {
try {
final response = await _dio.get('$_baseUrl/me');
if (response.statusCode == 200 && response.data['success'] == true) {
return response.data['user'];
}
return null;
} catch (e) {
LogManager().addLog('[Auth] Get User Info Failed: $e');
return null;
}
}
//
Future<bool> withdrawAccount() async {
try {
final response = await _dio.delete('$_baseUrl/withdraw');
if (response.statusCode == 200 && response.data['success'] == true) {
await _googleSignIn.signOut();
await _auth.signOut();
await _storage.deleteAll();
return true;
}
return false;
} catch (e) {
LogManager().addLog('[Auth] Withdraw Account Failed: $e');
return false;
}
}
Dio get dio => _dio;
} }

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class AppColors {
// : #1F1F1F ( '기본색')
static const Color text = Color(0xFF1F1F1F);
// (Old alias for compatibility, if needed)
static const Color basic = text;
// : #C8C8C8
static const Color inactive = Color(0xFFC8C8C8);
// : #FF7500 (, !)
static const Color highlight = Color(0xFFFF7500);
}

View File

@ -0,0 +1,24 @@
import 'package:flutter/foundation.dart';
class LogManager {
static final LogManager _instance = LogManager._internal();
factory LogManager() => _instance;
LogManager._internal();
final ValueNotifier<List<String>> logs = ValueNotifier([]);
void addLog(String message) {
try {
final timestamp = DateTime.now().toString().split(' ')[1].split('.')[0];
final logMessage = "[$timestamp] $message";
logs.value = [logMessage, ...logs.value];
} catch (e) {
print('LogManager Error: $e');
}
}
void clear() {
logs.value = [];
}
}

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -7,12 +7,14 @@ import Foundation
import firebase_auth import firebase_auth
import firebase_core import firebase_core
import flutter_secure_storage_macos
import google_sign_in_ios import google_sign_in_ios
import video_player_avfoundation import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
} }

View File

@ -49,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -57,6 +65,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -73,6 +89,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.8"
dio:
dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -81,6 +113,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.5"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
firebase_auth: firebase_auth:
dependency: "direct main" dependency: "direct main"
description: description:
@ -142,6 +190,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
url: "https://pub.dev"
source: hosted
version: "1.2.3"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
@ -160,6 +256,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_identity_services_web: google_identity_services_web:
dependency: transitive dependency: transitive
description: description:
@ -208,6 +312,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.4+4" version: "0.12.4+4"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
html: html:
dependency: transitive dependency: transitive
description: description:
@ -232,6 +344,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -264,6 +384,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -288,6 +416,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e"
url: "https://pub.dev"
source: hosted
version: "9.2.3"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -304,6 +456,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -312,6 +512,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -320,6 +528,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -469,6 +685,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev"
source: hosted
version: "5.15.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -477,6 +709,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.6.1" version: "6.6.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks: sdks:
dart: ">=3.10.7 <4.0.0" dart: ">=3.10.7 <4.0.0"
flutter: ">=3.38.0" flutter: ">=3.38.4"

View File

@ -39,6 +39,8 @@ dependencies:
firebase_core: ^3.0.0 # 파이어베이스 기본 (Updated) firebase_core: ^3.0.0 # 파이어베이스 기본 (Updated)
firebase_auth: ^5.0.0 # 인증 기능 (Updated) firebase_auth: ^5.0.0 # 인증 기능 (Updated)
google_sign_in: ^6.2.1 # 구글 로그인 UI/기능 (Updated) google_sign_in: ^6.2.1 # 구글 로그인 UI/기능 (Updated)
dio: ^5.4.0
flutter_secure_storage: ^9.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -66,6 +68,8 @@ flutter:
weight: 700 weight: 700
- asset: assets/fonts/SCDream-medium.otf - asset: assets/fonts/SCDream-medium.otf
weight: 500 weight: 500
- asset: assets/fonts/SCDream-regular.otf
weight: 400
assets: assets:
- assets/img/ - assets/img/

View File

@ -11,20 +11,11 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:app/main.dart'; import 'package:app/main.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('App starts with Splash Screen', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(const MyApp()); await tester.pumpWidget(const RupApp());
// Verify that our counter starts at 0. // Verify that Splash Screen is shown
expect(find.text('0'), findsOneWidget); expect(find.byType(MaterialApp), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
}); });
} }

View File

@ -8,10 +8,13 @@
#include <firebase_auth/firebase_auth_plugin_c_api.h> #include <firebase_auth/firebase_auth_plugin_c_api.h>
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FirebaseAuthPluginCApiRegisterWithRegistrar( FirebaseAuthPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar( FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
} }

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
firebase_auth firebase_auth
firebase_core firebase_core
flutter_secure_storage_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

3
backend/.dockerignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
npm-debug.log
.env

40
backend/config/db.js Normal file
View File

@ -0,0 +1,40 @@
const { Sequelize } = require('sequelize');
require('dotenv').config();
const sequelize = new Sequelize(
process.env.MYSQL_DATABASE,
process.env.MYSQL_USER,
process.env.MYSQL_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql',
logging: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
);
// Test Connection
const connectDB = async () => {
let retries = 20; // Increased retries for slow DB startup
while (retries > 0) {
try {
console.log(`[Database] Attempting to connect... (Retries left: ${retries})`);
await sequelize.authenticate();
console.log('[Database] Connection has been established successfully.');
return;
} catch (error) {
console.error(`[Database] Unable to connect. Retrying in 5s...`, error.message);
retries -= 1;
await new Promise(res => setTimeout(res, 5000)); // Wait 5 seconds
}
}
console.error('[Database] Failed to connect after multiple attempts.');
process.exit(1);
};
module.exports = { sequelize, connectDB };

View File

@ -0,0 +1,285 @@
const { OAuth2Client } = require('google-auth-library');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
require('dotenv').config();
// Google Client ID should be in env, but for now assuming it handles verification
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
const generateTokens = (user) => {
const payload = { id: user.id, email: user.email, nickname: user.nickname };
const accessToken = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: '1h',
});
const refreshToken = jwt.sign({ id: user.id }, process.env.JWT_REFRESH_SECRET, {
expiresIn: '14d',
});
return { accessToken, refreshToken };
};
// Generic Social Login Logic
const socialLogin = async (provider, socialInfo, res) => {
try {
const { socialId, email, nickname } = socialInfo;
// Find or Create User
const [user, created] = await User.findOrCreate({
where: { provider, socialId },
defaults: { email, nickname },
});
if (!created) {
// Update info if needed (e.g., changed nickname on social side) - Optional
user.email = email || user.email;
user.nickname = nickname || user.nickname;
}
// Generate Tokens
const { accessToken, refreshToken } = generateTokens(user);
// Save Refresh Token to DB
user.refreshToken = refreshToken;
await user.save();
console.log(`[Auth] User ${user.id} logged in via ${provider}`);
return res.status(200).json({
success: true,
accessToken,
refreshToken,
isNewUser: created, // 신규 가입 여부
user: {
id: user.id,
email: user.email,
nickname: user.nickname,
},
});
} catch (error) {
console.error('[Auth Error]', error);
return res.status(500).json({ success: false, message: 'Internal Server Error' });
}
};
// Google Login Handler
// Google Login Handler (Check only, or Login if exists)
exports.loginWithGoogle = async (req, res) => {
const { idToken } = req.body;
if (!idToken) {
return res.status(400).json({ success: false, message: 'Missing idToken' });
}
try {
// Verify Google Token
const ticket = await googleClient.verifyIdToken({
idToken,
audience: [
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_ANDROID_CLIENT_ID
],
});
const payload = ticket.getPayload();
const socialId = payload.sub;
// Check if user exists
const user = await User.findOne({ where: { provider: 'google', socialId } });
if (user) {
// User exists -> Login
const { accessToken, refreshToken } = generateTokens(user);
user.refreshToken = refreshToken;
await user.save();
console.log(`[Auth] User ${user.id} logged in via google`);
return res.status(200).json({
success: true,
accessToken,
refreshToken,
isNewUser: false,
user: {
id: user.id,
email: user.email,
nickname: user.nickname,
},
});
} else {
// User does not exist -> Return isNewUser: true (Do NOT create yet)
return res.status(200).json({
success: true,
isNewUser: true,
// Optional: Return partial info if needed for UI (e.g. pre-filling name)
email: payload.email,
nickname: payload.name,
});
}
} catch (error) {
console.error('[Google Verify Error]', error);
return res.status(401).json({
success: false,
message: 'Invalid Google Token',
debug: error.message
});
}
};
// Google Register Handler (Create User)
exports.registerWithGoogle = async (req, res) => {
const { idToken } = req.body;
if (!idToken) {
return res.status(400).json({ success: false, message: 'Missing idToken' });
}
try {
// Verify Google Token again
const ticket = await googleClient.verifyIdToken({
idToken,
audience: [
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_ANDROID_CLIENT_ID
],
});
const payload = ticket.getPayload();
const socialId = payload.sub;
const email = payload.email;
const nickname = payload.name;
// Find or Create User
const [user, created] = await User.findOrCreate({
where: { provider: 'google', socialId },
defaults: { email, nickname },
});
// If existing user calls register, just log them in (idempotent)
const { accessToken, refreshToken } = generateTokens(user);
user.refreshToken = refreshToken;
await user.save();
console.log(`[Auth] User ${user.id} registered/logged in via google`);
return res.status(200).json({
success: true,
accessToken,
refreshToken,
isNewUser: created,
user: {
id: user.id,
email: user.email,
nickname: user.nickname,
},
});
} catch (error) {
console.error('[Google Register Error]', error);
return res.status(401).json({
success: false,
message: 'Invalid Google Token',
debug: error.message
});
}
};
// Test Login Handler (For verification only)
exports.testLogin = async (req, res) => {
const { email, nickname } = req.body;
if (!email || !nickname) {
return res.status(400).json({ success: false, message: 'Missing email or nickname' });
}
// Use 'google' as provider to satisfy DB Enum constraint
const socialId = `test_${email}`;
return socialLogin('google', { socialId, email, nickname }, res);
};
// Refresh Token Handler
exports.refreshToken = async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({ success: false, message: 'Refresh Token required' });
}
try {
const secret = process.env.JWT_REFRESH_SECRET;
// 1. Verify Refresh Token
const decoded = jwt.verify(refreshToken, secret);
// 2. Check DB
const user = await User.findByPk(decoded.id);
if (!user || user.refreshToken !== refreshToken) {
return res.status(403).json({ success: false, message: 'Invalid Refresh Token' });
}
// 3. Issue new Access Token
const payload = { id: user.id, email: user.email, nickname: user.nickname };
const newAccessToken = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: '1h',
});
console.log(`[Auth] Access Token refreshed for User ${user.id}`);
return res.status(200).json({
success: true,
accessToken: newAccessToken,
});
} catch (error) {
console.error('[Refresh Error]', error);
if (error.name === 'TokenExpiredError') {
return res.status(403).json({ success: false, message: 'Refresh Token expired' });
}
return res.status(403).json({ success: false, message: 'Invalid Refresh Token' });
}
};
// Get User Info
exports.getMe = async (req, res) => {
try {
// req.user is set by middleware
const user = await User.findByPk(req.user.id);
if (!user) {
return res.status(404).json({ success: false, message: 'User not found' });
}
return res.status(200).json({
success: true,
user: {
id: user.id,
email: user.email,
nickname: user.nickname,
},
});
} catch (error) {
console.error('[GetMe Error]', error);
return res.status(500).json({ success: false, message: 'Internal Server Error' });
}
};
// Withdraw (Delete Account)
exports.withdraw = async (req, res) => {
try {
const userId = req.user.id;
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({ success: false, message: 'User not found' });
}
// Hard Delete
await user.destroy();
console.log(`[Auth] User ${userId} withdrew from the service.`);
return res.status(200).json({ success: true, message: 'Account deleted successfully' });
} catch (error) {
console.error('[Withdraw Error]', error);
return res.status(500).json({ success: false, message: 'Internal Server Error' });
}
};

View File

@ -2,7 +2,6 @@ FROM node:24-alpine
WORKDIR /app WORKDIR /app
# 패키지 파일 복사
# 패키지 파일 복사 # 패키지 파일 복사
COPY package*.json ./ COPY package*.json ./

View File

@ -1,11 +1,34 @@
const express = require('express'); const express = require('express');
const cors = require('cors');
const { connectDB, sequelize } = require('./config/db');
const authRoutes = require('./routes/auth');
const app = express(); const app = express();
const port = 3000; const port = 3000;
// Middleware
app.use(cors());
app.use(express.json()); // Body parser for JSON
// Routes
app.use('/auth', authRoutes);
app.get('/', (req, res) => { app.get('/', (req, res) => {
res.send('Hello from Express Backend!'); res.send('Hello from Express Backend!');
}); });
app.listen(port, '0.0.0.0', () => { // Database Connection & Server Start
const startServer = async () => {
await connectDB();
// Sync models (in production, use migration instead of sync({alter: true}))
// For dev: force: false to keep data, alter: true to update schema
await sequelize.sync({ alter: true });
console.log('Database synced');
app.listen(port, '0.0.0.0', () => {
console.log(`Backend app listening on port ${port}`); console.log(`Backend app listening on port ${port}`);
}); });
};
startServer();

View File

@ -0,0 +1,26 @@
const jwt = require('jsonwebtoken');
require('dotenv').config();
exports.verifyToken = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ success: false, message: 'No token provided' });
}
const token = authHeader.split(' ')[1]; // "Bearer <token>"
if (!token) {
return res.status(401).json({ success: false, message: 'Invalid token format' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // { id, email, nickname, ... }
next();
} catch (error) {
console.error('[Auth Middleware] Token Verification Failed:', error.message);
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ success: false, message: 'Token expired' });
}
return res.status(401).json({ success: false, message: 'Invalid token' });
}
};

46
backend/models/user.js Normal file
View File

@ -0,0 +1,46 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/db');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
email: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isEmail: true,
},
},
nickname: {
type: DataTypes.STRING,
allowNull: true,
},
provider: {
type: DataTypes.ENUM('google', 'naver', 'kakao'),
allowNull: false,
comment: 'Social login provider',
},
socialId: {
type: DataTypes.STRING,
allowNull: false,
comment: 'Unique ID from the social provider',
},
refreshToken: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'Generic refresh token for valid session',
},
}, {
indexes: [
{
unique: true,
fields: ['provider', 'socialId'], // Prevent duplicate users for same provider
},
],
timestamps: true,
});
module.exports = User;

1541
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,12 @@
"start": "node index.js" "start": "node index.js"
}, },
"dependencies": { "dependencies": {
"express": "^4.18.2" "cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"google-auth-library": "^9.4.1",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.6.5",
"sequelize": "^6.35.1"
} }
} }

23
backend/routes/auth.js Normal file
View File

@ -0,0 +1,23 @@
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
router.post('/google', authController.loginWithGoogle);
router.post('/google/register', authController.registerWithGoogle); // Add register route
router.post('/test-login', authController.testLogin);
router.post('/refresh', authController.refreshToken);
const verifyToken = require('../middleware/authMiddleware').verifyToken; // Correct import
router.get('/test-protected', verifyToken, (req, res) => {
res.json({ success: true, message: 'You have a valid token', user: req.user });
});
router.get('/me', verifyToken, authController.getMe);
router.delete('/withdraw', verifyToken, authController.withdraw);
// Future place for:
// router.post('/kakao', authController.loginWithKakao);
// router.post('/naver', authController.loginWithNaver);
module.exports = router;

View File

@ -1,4 +1,4 @@
version: "3.8"
services: services:
db: db:
@ -27,6 +27,7 @@ services:
restart: always restart: always
ports: ports:
- "3000:3000" - "3000:3000"
env_file: env_file:
- ./backend/.env.production - ./backend/.env.production
depends_on: depends_on:

23
reinstall.bat Normal file
View File

@ -0,0 +1,23 @@
@echo off
cd /d "%~dp0"
echo ==============================================
echo [RUP Project] Cleaning and Rebuilding Backend
echo ==============================================
echo 1. Stopping Docker services...
docker compose down
echo.
echo 2. Forcing removal of old backend image (to clear cache)...
docker image rm -f rup-backend
echo.
echo 3. Rebuilding Docker containers...
docker compose up -d --build
echo.
echo 4. Checking logs (Press Ctrl+C to exit log view)...
echo ==============================================
docker compose logs -f backend
pause