├── .github
└── ISSUE_TEMPLATE
│ └── feature_request.md
├── .gitignore
├── Android
├── FunBox
│ ├── .gitignore
│ ├── app
│ │ ├── .gitignore
│ │ ├── build.gradle.kts
│ │ ├── google-services.json
│ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ ├── gradlew
│ │ ├── gradlew.bat
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ │ ├── androidTest
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── rpg
│ │ │ │ └── funbox
│ │ │ │ └── ExampleInstrumentedTest.kt
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── ic_funbox-playstore.png
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── rpg
│ │ │ │ │ └── funbox
│ │ │ │ │ ├── app
│ │ │ │ │ ├── MainApplication.kt
│ │ │ │ │ └── MySharedPreferences.kt
│ │ │ │ │ ├── data
│ │ │ │ │ ├── JWTInterceptor.kt
│ │ │ │ │ ├── JwtDecoder.kt
│ │ │ │ │ ├── OkHttpClientInstance.kt
│ │ │ │ │ ├── RetrofitInstance.kt
│ │ │ │ │ ├── dto
│ │ │ │ │ │ ├── NaverAccessTokenRequest.kt
│ │ │ │ │ │ ├── NaverAccessTokenResponse.kt
│ │ │ │ │ │ ├── NaverLoginRequest.kt
│ │ │ │ │ │ ├── User.kt
│ │ │ │ │ │ ├── UserAuthDto.kt
│ │ │ │ │ │ ├── UserDetail.kt
│ │ │ │ │ │ ├── UserInfoRequest.kt
│ │ │ │ │ │ ├── UserInfoResponse.kt
│ │ │ │ │ │ ├── UserLocation.kt
│ │ │ │ │ │ ├── UserLocationResponse.kt
│ │ │ │ │ │ └── UsersLocationRequest.kt
│ │ │ │ │ ├── network
│ │ │ │ │ │ └── service
│ │ │ │ │ │ │ ├── NaverLoginApi.kt
│ │ │ │ │ │ │ ├── SignUpApi.kt
│ │ │ │ │ │ │ ├── UserInfoApi.kt
│ │ │ │ │ │ │ └── UsersLocationApi.kt
│ │ │ │ │ └── repository
│ │ │ │ │ │ ├── NaverLoginRepository.kt
│ │ │ │ │ │ ├── NaverLoginRepositoryImpl.kt
│ │ │ │ │ │ ├── UserRepository.kt
│ │ │ │ │ │ ├── UserRepositoryImpl.kt
│ │ │ │ │ │ ├── UsersLocationRepository.kt
│ │ │ │ │ │ └── UsersLocationRepositoryImpl.kt
│ │ │ │ │ └── presentation
│ │ │ │ │ ├── ActivityExtension.kt
│ │ │ │ │ ├── BaseDialogFragment.kt
│ │ │ │ │ ├── BaseFragment.kt
│ │ │ │ │ ├── CustomNaverMap.kt
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ ├── MapSocket.kt
│ │ │ │ │ ├── game
│ │ │ │ │ ├── GameActivity.kt
│ │ │ │ │ ├── gameselect
│ │ │ │ │ │ ├── GameListAdapter.kt
│ │ │ │ │ │ ├── GameSelectBindingAdapters.kt
│ │ │ │ │ │ ├── GameSelectFragment.kt
│ │ │ │ │ │ ├── GameSelectUiEvent.kt
│ │ │ │ │ │ ├── GameSelectUiState.kt
│ │ │ │ │ │ ├── GameSelectViewModel.kt
│ │ │ │ │ │ ├── OnGameClickListener.kt
│ │ │ │ │ │ └── QuestionGameViewModel.kt
│ │ │ │ │ ├── quiz
│ │ │ │ │ │ ├── AnswerCheckFragment.kt
│ │ │ │ │ │ ├── ChatAdapter.kt
│ │ │ │ │ │ ├── LoadingDialog.kt
│ │ │ │ │ │ ├── MessageItem.kt
│ │ │ │ │ │ ├── NetworkAlertFragment.kt
│ │ │ │ │ │ ├── QuizBindingAdapters.kt
│ │ │ │ │ │ ├── QuizFragment.kt
│ │ │ │ │ │ ├── QuizUiEvent.kt
│ │ │ │ │ │ ├── QuizUiState.kt
│ │ │ │ │ │ ├── QuizViewModel.kt
│ │ │ │ │ │ └── ScoreBoardFragment.kt
│ │ │ │ │ └── wait
│ │ │ │ │ │ └── WaitFragment.kt
│ │ │ │ │ ├── login
│ │ │ │ │ ├── AccessPermission.kt
│ │ │ │ │ ├── TitleActivity.kt
│ │ │ │ │ ├── nickname
│ │ │ │ │ │ ├── NicknameFragment.kt
│ │ │ │ │ │ ├── NicknameUiEvent.kt
│ │ │ │ │ │ └── NicknameUiState.kt
│ │ │ │ │ ├── profile
│ │ │ │ │ │ ├── ProfileBindingAdapters.kt
│ │ │ │ │ │ ├── ProfileFragment.kt
│ │ │ │ │ │ ├── ProfileUiEvent.kt
│ │ │ │ │ │ └── ProfileUiState.kt
│ │ │ │ │ ├── splash
│ │ │ │ │ │ ├── SplashActivity.kt
│ │ │ │ │ │ ├── SplashUiEvent.kt
│ │ │ │ │ │ └── SplashViewModel.kt
│ │ │ │ │ └── title
│ │ │ │ │ │ ├── TitleBindingAdapters.kt
│ │ │ │ │ │ ├── TitleFragment.kt
│ │ │ │ │ │ ├── TitleUiEvent.kt
│ │ │ │ │ │ ├── TitleUiState.kt
│ │ │ │ │ │ └── TitleViewModel.kt
│ │ │ │ │ ├── map
│ │ │ │ │ ├── GameData.kt
│ │ │ │ │ ├── GetGameDialog.kt
│ │ │ │ │ ├── MapBindingAdapters.kt
│ │ │ │ │ ├── MapFragment.kt
│ │ │ │ │ ├── MapProfile.kt
│ │ │ │ │ ├── MapProfileAdapter.kt
│ │ │ │ │ ├── MapUiEvent.kt
│ │ │ │ │ ├── MapViewModel.kt
│ │ │ │ │ ├── MessageDialog.kt
│ │ │ │ │ ├── OtherUserState.kt
│ │ │ │ │ └── SocketApplication.kt
│ │ │ │ │ └── setting
│ │ │ │ │ ├── SetNameDialog.kt
│ │ │ │ │ ├── SetProfileDialog.kt
│ │ │ │ │ ├── SettingBindingAdapter.kt
│ │ │ │ │ ├── SettingFragment.kt
│ │ │ │ │ ├── SettingUiEvent.kt
│ │ │ │ │ ├── SettingUiState.kt
│ │ │ │ │ ├── SettingViewModel.kt
│ │ │ │ │ └── WithdrawalDialog.kt
│ │ │ └── res
│ │ │ │ ├── anim
│ │ │ │ ├── fade_in.xml
│ │ │ │ ├── fade_out.xml
│ │ │ │ ├── slide_left_enter.xml
│ │ │ │ └── slide_left_exit.xml
│ │ │ │ ├── drawable
│ │ │ │ ├── add_24.xml
│ │ │ │ ├── answer_edittext.xml
│ │ │ │ ├── autorenew_24.xml
│ │ │ │ ├── baseline_arrow_back_24.xml
│ │ │ │ ├── bg_dialog_rectangle.xml
│ │ │ │ ├── bg_green_rectangle.xml
│ │ │ │ ├── bg_input_text.xml
│ │ │ │ ├── bg_red_rectangle.xml
│ │ │ │ ├── close_24.xml
│ │ │ │ ├── fail_button.xml
│ │ │ │ ├── funbox_logo.png
│ │ │ │ ├── group_1940.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ ├── map_24.xml
│ │ │ │ ├── menu_24.xml
│ │ │ │ ├── my_location.png
│ │ │ │ ├── naver_login.png
│ │ │ │ ├── navi_icon.png
│ │ │ │ ├── navigate_next_24.xml
│ │ │ │ ├── nickname_edittext.xml
│ │ │ │ ├── other_location.png
│ │ │ │ ├── profile_add.png
│ │ │ │ ├── profile_game.xml
│ │ │ │ ├── profile_none.png
│ │ │ │ ├── round_radius.xml
│ │ │ │ ├── rounded_corner_rect_shadow.xml
│ │ │ │ ├── settings_24.xml
│ │ │ │ ├── splash_background.xml
│ │ │ │ ├── success_button.xml
│ │ │ │ ├── wait_background.xml
│ │ │ │ └── write_24.xml
│ │ │ │ ├── layout
│ │ │ │ ├── activity_game.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── activity_title.xml
│ │ │ │ ├── dialog_get_game.xml
│ │ │ │ ├── dialog_loading.xml
│ │ │ │ ├── dialog_message.xml
│ │ │ │ ├── dialog_positive.xml
│ │ │ │ ├── dialog_profile_change.xml
│ │ │ │ ├── dialog_red.xml
│ │ │ │ ├── dialog_red_with_text.xml
│ │ │ │ ├── fragment_answer_check.xml
│ │ │ │ ├── fragment_game_select.xml
│ │ │ │ ├── fragment_map.xml
│ │ │ │ ├── fragment_network_alert.xml
│ │ │ │ ├── fragment_nickname.xml
│ │ │ │ ├── fragment_profile.xml
│ │ │ │ ├── fragment_quiz.xml
│ │ │ │ ├── fragment_score_board.xml
│ │ │ │ ├── fragment_setting.xml
│ │ │ │ ├── fragment_title.xml
│ │ │ │ ├── fragment_wait.xml
│ │ │ │ ├── item_chat.xml
│ │ │ │ ├── item_chat_other.xml
│ │ │ │ ├── item_game_list.xml
│ │ │ │ ├── map_profile.xml
│ │ │ │ ├── menu_header.xml
│ │ │ │ ├── quiz_question_count_spinner.xml
│ │ │ │ └── to_be_determined_list.xml
│ │ │ │ ├── menu
│ │ │ │ └── side_menu.xml
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_funbox.xml
│ │ │ │ └── ic_funbox_round.xml
│ │ │ │ ├── mipmap-anydpi
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_funbox.webp
│ │ │ │ ├── ic_funbox_foreground.webp
│ │ │ │ ├── ic_funbox_round.webp
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_funbox.webp
│ │ │ │ ├── ic_funbox_foreground.webp
│ │ │ │ ├── ic_funbox_round.webp
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_funbox.webp
│ │ │ │ ├── ic_funbox_foreground.webp
│ │ │ │ ├── ic_funbox_round.webp
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_funbox.webp
│ │ │ │ ├── ic_funbox_foreground.webp
│ │ │ │ ├── ic_funbox_round.webp
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_funbox.webp
│ │ │ │ ├── ic_funbox_foreground.webp
│ │ │ │ ├── ic_funbox_round.webp
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── navigation
│ │ │ │ ├── game_nav_graph.xml
│ │ │ │ ├── main_navi_graph.xml
│ │ │ │ └── title_nav_graph.xml
│ │ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── ic_funbox_background.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── themes.xml
│ │ │ │ └── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── rpg
│ │ │ └── funbox
│ │ │ └── ExampleUnitTest.kt
│ ├── build.gradle.kts
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle.kts
└── README.md
├── BackEnd
├── README.md
└── funbox
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .husky
│ └── pre-commit
│ ├── .prettierrc
│ ├── README.md
│ ├── nest-cli.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── app.module.ts
│ ├── auth
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto
│ │ │ ├── auth-request.dto.ts
│ │ │ ├── auth-response.dto.ts
│ │ │ └── user-auth.dto.ts
│ │ └── jwt.strategy.ts
│ ├── configs
│ │ ├── swagger.config.ts
│ │ └── typeorm.config.ts
│ ├── main.ts
│ ├── socket
│ │ ├── dto
│ │ │ └── game-room.dto.ts
│ │ ├── enum
│ │ │ └── game-apply-answer.enum.ts
│ │ ├── filters
│ │ │ └── ws-exception.filter.ts
│ │ ├── game.service.ts
│ │ ├── pipes
│ │ │ └── answer-validation.pipe.ts
│ │ ├── socket.gateway.ts
│ │ ├── socket.module.ts
│ │ └── socket.service.ts
│ └── users
│ │ ├── dto
│ │ ├── near-users.dto.ts
│ │ ├── user-location.dto.ts
│ │ ├── user-request.dto.ts
│ │ └── user-response.dto.ts
│ │ ├── user.entity.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ └── users.service.ts
│ ├── test
│ ├── test.png
│ ├── test_apis.js
│ ├── test_auth.js
│ ├── test_date.js
│ └── test_profile.js
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── README.md
└── docs
└── pull_request_template.md
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Issue 타입(하나 이상의 Issue 타입을 선택해주세요)
11 | - [ ] 기능 추가
12 | - [ ] 기능 삭제
13 | - [ ] 버그 수정
14 | - [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트
15 |
16 | ### 상세 내용
17 | ex) Github 소셜 로그인 기능이 필요합니다.
18 |
19 | ### 예상 소요 시간
20 | - [ ] `0.5h`
21 | - [ ] `1h`
22 | - [ ] `1.5h`
23 | - [ ] `2h`
24 | - [ ] `2.5h`
25 | - [ ] `3h`
26 |
27 | ### 라벨
28 | - 예상 소요 시간: `E: 1h`
29 | - 그룹: `client`, `server`
30 | - 긴급도: `High`, `Middle`, `Low`
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Android/FunBox/.idea/
2 |
--------------------------------------------------------------------------------
/Android/FunBox/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /key.jks
4 | /key.properties
5 | /local.properties
6 | /.idea/caches
7 | /.idea/libraries
8 | /.idea/modules.xml
9 | /.idea/workspace.xml
10 | /.idea/navEditor.xml
11 | /.idea/assetWizardSettings.xml
12 | /.idea/misc.xml
13 | .DS_Store
14 | /build
15 | /release
16 | /captures
17 | .externalNativeBuild
18 | .cxx
19 | local.properties
20 |
--------------------------------------------------------------------------------
/Android/FunBox/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
--------------------------------------------------------------------------------
/Android/FunBox/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "897486577056",
4 | "project_id": "funbox-f4840",
5 | "storage_bucket": "funbox-f4840.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:897486577056:android:d693940bacaab95d2a98c5",
11 | "android_client_info": {
12 | "package_name": "com.rpg.funbox"
13 | }
14 | },
15 | "oauth_client": [],
16 | "api_key": [
17 | {
18 | "current_key": "AIzaSyAelvQzX6nCyGANdZlNXxuVimknV7FAmRY"
19 | }
20 | ],
21 | "services": {
22 | "appinvite_service": {
23 | "other_platform_oauth_client": []
24 | }
25 | }
26 | }
27 | ],
28 | "configuration_version": "1"
29 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Android/FunBox/app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/Android/FunBox/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class com.google.android.gms.location.** { *; }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/androidTest/java/com/rpg/funbox/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.funbox", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
32 |
33 |
37 |
38 |
41 |
42 |
45 |
46 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/ic_funbox-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/ic_funbox-playstore.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/app/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.app
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import timber.log.Timber
6 |
7 | class MainApplication : Application() {
8 |
9 | init {
10 | instance = this
11 | }
12 |
13 | override fun onCreate() {
14 | mySharedPreferences = MySharedPreferences()
15 | super.onCreate()
16 |
17 | Timber.plant(Timber.DebugTree())
18 | }
19 |
20 | companion object {
21 | lateinit var mySharedPreferences: MySharedPreferences
22 | var instance: MainApplication? = null
23 |
24 | fun myContext(): Context {
25 | return instance!!.applicationContext
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/app/MySharedPreferences.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.app
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | class MySharedPreferences {
7 |
8 | private val preferences: SharedPreferences =
9 | MainApplication.myContext().getSharedPreferences("prefs_name", Context.MODE_PRIVATE)
10 |
11 | fun getJWT(key: String, defValue: String): String {
12 | return preferences.getString(key, defValue).toString()
13 | }
14 |
15 | fun setJWT(key: String, defValue: String) {
16 | preferences.edit().putString(key, defValue).apply()
17 | }
18 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/JWTInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data
2 |
3 | import com.rpg.funbox.app.MainApplication
4 | import okhttp3.Interceptor
5 | import okhttp3.Response
6 | import timber.log.Timber
7 | import java.io.IOException
8 |
9 | object JWTInterceptor : Interceptor {
10 |
11 | @Throws(IOException::class)
12 | override fun intercept(chain: Interceptor.Chain): Response = with(chain) {
13 | val accessToken: String = MainApplication.mySharedPreferences.getJWT("jwt", "")
14 | Timber.d("JWTInterceptor: $accessToken")
15 | val newRequest = request().newBuilder()
16 | .addHeader("Authorization", "Bearer $accessToken")
17 | .build()
18 | proceed(newRequest)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/JwtDecoder.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data
2 |
3 | import android.util.Base64
4 | import com.google.gson.JsonParser
5 | import com.rpg.funbox.data.dto.UserAuthDto
6 | import java.nio.charset.StandardCharsets
7 |
8 | object JwtDecoder {
9 |
10 | //private val gson = Gson()
11 | //private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
12 |
13 | fun getUser(accessToken: String): UserAuthDto {
14 | val payloadJwt = accessToken.split(".")[1]
15 | val decodedPayload =
16 | String(Base64.decode(payloadJwt, Base64.DEFAULT), StandardCharsets.UTF_8)
17 | val jsonObject = JsonParser.parseString(decodedPayload).asJsonObject
18 |
19 | return UserAuthDto(
20 | jsonObject.get("id").asInt,
21 | jsonObject.get("iat").asInt,
22 | jsonObject.get("exp").asInt
23 | )
24 |
25 | /*
26 | val json = gson.toJson(jsonObject, JwtDecoder::class.java)
27 |
28 | val moshiAdapter: JsonAdapter = moshi.adapter(UserAuthDto::class.java)
29 | return moshiAdapter.fromJsonValue(decodedPayload)
30 | */
31 | }
32 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/OkHttpClientInstance.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data
2 |
3 | import okhttp3.OkHttpClient
4 | import java.util.concurrent.TimeUnit
5 |
6 | object OkHttpClientInstance {
7 |
8 | val okHttpClient: OkHttpClient by lazy {
9 | OkHttpClient.Builder().run {
10 | this.addInterceptor(JWTInterceptor)
11 | this.build()
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/RetrofitInstance.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data
2 |
3 | import com.squareup.moshi.Moshi
4 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
5 | import retrofit2.Retrofit
6 | import retrofit2.converter.moshi.MoshiConverterFactory
7 |
8 | object RetrofitInstance {
9 |
10 | private const val FUNBOX_BASE_URL = "http://175.45.193.191:3000"
11 | private val moshi = Moshi.Builder()
12 | .add(KotlinJsonAdapterFactory())
13 | .build()
14 |
15 | val retrofit: Retrofit by lazy {
16 | Retrofit.Builder()
17 | .baseUrl(FUNBOX_BASE_URL)
18 | .client(OkHttpClientInstance.okHttpClient)
19 | .addConverterFactory(MoshiConverterFactory.create(moshi))
20 | .build()
21 | }
22 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/NaverAccessTokenRequest.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class NaverAccessTokenRequest(
4 | val naverAccessToken: String
5 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/NaverAccessTokenResponse.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class NaverAccessTokenResponse(
4 | val accessToken: String
5 | )
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/NaverLoginRequest.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class NaverLoginRequest(
4 | val userId: String
5 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/User.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | import com.naver.maps.geometry.LatLng
4 | import com.naver.maps.map.overlay.InfoWindow
5 | import com.naver.maps.map.overlay.Marker
6 |
7 | data class User(
8 | val status: Int,
9 | val id: Int,
10 | var loc: LatLng,
11 | var name: String?,
12 | var isMsg: Boolean,
13 | var mapPin: Marker? = null
14 | )
15 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UserAuthDto.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | import com.squareup.moshi.Json
4 |
5 | data class UserAuthDto(
6 | @Json(name = "id")
7 | val id: Int,
8 |
9 | @Json(name = "iat")
10 | val iat: Int,
11 |
12 | @Json(name = "exp")
13 | val exp: Int
14 | )
15 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UserDetail.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class UserDetail (
4 | val id: Int,
5 | var msg: String,
6 | val profile: String,
7 | var name: String
8 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UserInfoRequest.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | import com.squareup.moshi.Json
4 |
5 | data class UserInfoRequest(
6 | @Json(name = "username")
7 | val userName: String?,
8 |
9 | @Json(name = "profileUrl")
10 | val profileUrl: String?,
11 |
12 | @Json(name = "message")
13 | val message: String?
14 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UserInfoResponse.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | import com.squareup.moshi.Json
4 |
5 | data class UserInfoResponse(
6 | @Json(name = "username")
7 | val userName: String?,
8 |
9 | @Json(name = "profileUrl")
10 | val profileUrl: String?,
11 |
12 | @Json(name = "message")
13 | val message: String?
14 | )
15 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UserLocation.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class UserLocation(
4 | val id: Int,
5 | val username: String?,
6 | val locX: Double?,
7 | val locY: Double?,
8 | val isMsgInAnHour: Boolean
9 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UserLocationResponse.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class UserLocationResponse(
4 | val resultMessage: String,
5 | val userLocations: List?
6 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/dto/UsersLocationRequest.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.dto
2 |
3 | data class UsersLocationRequest(
4 | val locX: Double,
5 | val locY: Double
6 | )
7 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/network/service/NaverLoginApi.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.network.service
2 |
3 | import com.rpg.funbox.data.dto.NaverAccessTokenRequest
4 | import com.rpg.funbox.data.dto.NaverAccessTokenResponse
5 | import retrofit2.Response
6 | import retrofit2.http.Body
7 | import retrofit2.http.POST
8 |
9 | interface NaverLoginApi {
10 |
11 | @POST("/auth/navertoken")
12 | suspend fun submitNaverAccessToken(
13 | @Body body: NaverAccessTokenRequest
14 | ): Response
15 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/network/service/SignUpApi.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.network.service
2 |
3 | import com.rpg.funbox.data.dto.UserInfoRequest
4 | import com.rpg.funbox.data.dto.UserInfoResponse
5 | import okhttp3.MultipartBody
6 | import retrofit2.Response
7 | import retrofit2.http.Body
8 | import retrofit2.http.DELETE
9 | import retrofit2.http.GET
10 | import retrofit2.http.Multipart
11 | import retrofit2.http.PATCH
12 | import retrofit2.http.POST
13 | import retrofit2.http.Part
14 |
15 | interface SignUpApi {
16 |
17 | @GET("/users")
18 | suspend fun fetchUserInfo(): Response
19 |
20 | @PATCH("/users/username")
21 | suspend fun submitUserName(
22 | @Body body: UserInfoRequest
23 | ): Response
24 |
25 | @Multipart
26 | @POST("/users/profile")
27 | suspend fun submitUserProfile(
28 | @Part file: MultipartBody.Part
29 | ): Response
30 |
31 | @DELETE("/users")
32 | suspend fun deleteUserInfo(): Response
33 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/network/service/UserInfoApi.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.network.service
2 |
3 | import com.rpg.funbox.data.dto.UserInfoRequest
4 | import com.rpg.funbox.data.dto.UserInfoResponse
5 | import retrofit2.Response
6 | import retrofit2.http.Body
7 | import retrofit2.http.GET
8 | import retrofit2.http.PATCH
9 | import retrofit2.http.Path
10 |
11 | interface UserInfoApi {
12 |
13 | @GET("/users/{id}")
14 | suspend fun fetchSpecificUser(
15 | @Path(value = "id") userId: Int
16 | ): Response
17 |
18 | @PATCH("/users/message")
19 | suspend fun submitUserMessage(
20 | @Body body: UserInfoRequest
21 | ): Response
22 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/network/service/UsersLocationApi.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.network.service
2 |
3 | import com.rpg.funbox.data.dto.UserLocation
4 | import com.rpg.funbox.data.dto.UsersLocationRequest
5 | import retrofit2.Response
6 | import retrofit2.http.Body
7 | import retrofit2.http.POST
8 |
9 | interface UsersLocationApi {
10 | @POST("/users/location")
11 | suspend fun fetchUsersLocation(
12 | @Body body: UsersLocationRequest
13 | ) : Response>
14 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/repository/NaverLoginRepository.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.repository
2 |
3 | import com.rpg.funbox.data.dto.UserAuthDto
4 |
5 | interface NaverLoginRepository {
6 |
7 | suspend fun postNaverAccessToken(token: String): UserAuthDto?
8 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/repository/NaverLoginRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.repository
2 |
3 | import com.rpg.funbox.app.MainApplication
4 | import com.rpg.funbox.data.JwtDecoder
5 | import com.rpg.funbox.data.RetrofitInstance
6 | import com.rpg.funbox.data.dto.NaverAccessTokenRequest
7 | import com.rpg.funbox.data.dto.UserAuthDto
8 | import com.rpg.funbox.data.network.service.NaverLoginApi
9 | import timber.log.Timber
10 |
11 | class NaverLoginRepositoryImpl : NaverLoginRepository {
12 |
13 | private val naverLoginApi: NaverLoginApi by lazy {
14 | RetrofitInstance.retrofit.create(NaverLoginApi::class.java)
15 | }
16 |
17 | override suspend fun postNaverAccessToken(token: String): UserAuthDto? {
18 | val response = naverLoginApi.submitNaverAccessToken(NaverAccessTokenRequest(token))
19 | Timber.d("${response.code()}")
20 | val body = response.body()
21 | Timber.d("$body")
22 | when (response.code()) {
23 | in successStatusCodeRange -> {
24 | return body?.let {
25 | Timber.d("JWT: ${it.accessToken}")
26 | MainApplication.mySharedPreferences.setJWT("jwt", it.accessToken)
27 | JwtDecoder.getUser(it.accessToken)
28 | }
29 | }
30 |
31 | UNAUTHORIZED_STATUS -> {
32 | if (body != null) {
33 | Timber.d("JWT: ${body.accessToken}")
34 | }
35 | return UserAuthDto(0, 0, 0)
36 | }
37 |
38 | in serverErrorStatusCodeRange -> {}
39 |
40 | else -> {}
41 | }
42 | return null
43 | }
44 |
45 | companion object {
46 | private const val UNAUTHORIZED_STATUS: Int = 401
47 |
48 | private val successStatusCodeRange: IntRange = 200..299
49 | private val serverErrorStatusCodeRange: IntRange = 500..599
50 | }
51 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/repository/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.repository
2 |
3 | import com.rpg.funbox.data.dto.UserInfoResponse
4 | import okhttp3.MultipartBody
5 |
6 | interface UserRepository {
7 |
8 | suspend fun getUserInfo(): UserInfoResponse?
9 |
10 | suspend fun getSpecificUserInfo(userId: Int): UserInfoResponse?
11 |
12 | suspend fun patchUserMessage(message: String): Boolean
13 |
14 | suspend fun patchUserName(userName: String): Boolean
15 |
16 | suspend fun postUserProfile(imageFile: MultipartBody.Part): Boolean
17 |
18 | suspend fun withdraw(): Boolean
19 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/repository/UserRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.repository
2 |
3 | import com.rpg.funbox.data.RetrofitInstance
4 | import com.rpg.funbox.data.dto.UserInfoRequest
5 | import com.rpg.funbox.data.dto.UserInfoResponse
6 | import com.rpg.funbox.data.network.service.SignUpApi
7 | import com.rpg.funbox.data.network.service.UserInfoApi
8 | import okhttp3.MultipartBody
9 | import timber.log.Timber
10 |
11 | class UserRepositoryImpl : UserRepository {
12 |
13 | private val signUpApi: SignUpApi by lazy {
14 | RetrofitInstance.retrofit.create(SignUpApi::class.java)
15 | }
16 |
17 | private val userInfoApi: UserInfoApi by lazy {
18 | RetrofitInstance.retrofit.create(UserInfoApi::class.java)
19 | }
20 |
21 | override suspend fun getUserInfo(): UserInfoResponse? {
22 | val response = signUpApi.fetchUserInfo()
23 | when (response.code()) {
24 | in successStatusCodeRange -> {
25 | return response.body()
26 | }
27 |
28 | else -> {}
29 | }
30 |
31 | return null
32 | }
33 |
34 | override suspend fun getSpecificUserInfo(userId: Int): UserInfoResponse? {
35 | val response = userInfoApi.fetchSpecificUser(userId = userId)
36 | when (response.code()) {
37 | in successStatusCodeRange -> {
38 | return response.body()
39 | }
40 |
41 | else -> {}
42 | }
43 |
44 | return null
45 | }
46 |
47 | override suspend fun patchUserMessage(message: String): Boolean {
48 | Timber.d("Message: $message")
49 | val response = userInfoApi.submitUserMessage(UserInfoRequest(null, null, message = message))
50 | Timber.d("Response: ${response.body()}")
51 | when (response.code()) {
52 | in successStatusCodeRange -> {
53 | return true
54 | }
55 |
56 | else -> {}
57 | }
58 |
59 | return false
60 | }
61 |
62 | override suspend fun patchUserName(userName: String): Boolean {
63 | val response = signUpApi.submitUserName(UserInfoRequest(userName = userName, null, null))
64 | when (response.code()) {
65 | in successStatusCodeRange -> {
66 | return true
67 | }
68 |
69 | else -> {}
70 | }
71 |
72 | return false
73 | }
74 |
75 | override suspend fun postUserProfile(imageFile: MultipartBody.Part): Boolean {
76 | val response = signUpApi.submitUserProfile(file = imageFile)
77 | when (response.code()) {
78 | in successStatusCodeRange -> {
79 | return true
80 | }
81 |
82 | else -> {}
83 | }
84 | return false
85 | }
86 |
87 | override suspend fun withdraw(): Boolean {
88 | val response = signUpApi.deleteUserInfo()
89 | when (response.code()) {
90 | in successStatusCodeRange -> {
91 | return true
92 | }
93 |
94 | else -> {}
95 | }
96 |
97 | return false
98 | }
99 |
100 | companion object {
101 | private const val UNAUTHORIZED_STATUS: Int = 401
102 |
103 | private val successStatusCodeRange: IntRange = 200..299
104 | private val serverErrorStatusCodeRange: IntRange = 500..599
105 | }
106 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/repository/UsersLocationRepository.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.repository
2 |
3 | import com.rpg.funbox.data.dto.UserLocationResponse
4 |
5 | interface UsersLocationRepository {
6 |
7 | suspend fun getUsersLocation(locX: Double, locY: Double): UserLocationResponse
8 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/data/repository/UsersLocationRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.data.repository
2 |
3 | import com.rpg.funbox.data.RetrofitInstance
4 | import com.rpg.funbox.data.dto.UserLocationResponse
5 | import com.rpg.funbox.data.dto.UsersLocationRequest
6 | import com.rpg.funbox.data.network.service.UsersLocationApi
7 | import timber.log.Timber
8 |
9 | class UsersLocationRepositoryImpl : UsersLocationRepository {
10 |
11 | private val usersLocationApi: UsersLocationApi by lazy {
12 | RetrofitInstance.retrofit.create(UsersLocationApi::class.java)
13 | }
14 |
15 | override suspend fun getUsersLocation(locX: Double, locY: Double): UserLocationResponse {
16 | val response = usersLocationApi.fetchUsersLocation(UsersLocationRequest(locX = locX, locY = locY))
17 | Timber.d("Response Code: ${response.code()}")
18 | when (response.code()) {
19 | in successStatusCodeRange -> {
20 | Timber.d("Success")
21 | return UserLocationResponse("OK", response.body())
22 | }
23 |
24 | UNAUTHORIZED_STATUS -> {
25 | Timber.d("Unauthorized")
26 | return UserLocationResponse("Unauthorized", null)
27 | }
28 |
29 | in serverErrorStatusCodeRange -> {
30 | Timber.d("Server Error")
31 | return UserLocationResponse("Server Error", null)
32 | }
33 |
34 | else -> {}
35 | }
36 |
37 | Timber.d("Else")
38 | return UserLocationResponse("Timeout", null)
39 | }
40 |
41 | companion object {
42 | private const val UNAUTHORIZED_STATUS: Int = 401
43 |
44 | private val successStatusCodeRange: IntRange = 200..299
45 | private val serverErrorStatusCodeRange: IntRange = 500..599
46 | }
47 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/ActivityExtension.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation
2 |
3 | import android.app.Activity
4 | import android.app.ActivityOptions
5 | import android.content.pm.PackageManager
6 | import android.os.Bundle
7 | import androidx.navigation.NavController
8 | import androidx.navigation.NavDirections
9 | import com.rpg.funbox.R
10 | import timber.log.Timber
11 |
12 | fun Activity.checkPermission(permissions: Array): Boolean {
13 | return permissions.all { permission ->
14 | checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
15 | }
16 | }
17 |
18 | fun Activity.slideLeft(): Bundle {
19 | return ActivityOptions.makeCustomAnimation(this, R.anim.slide_left_enter, R.anim.slide_left_exit).toBundle()
20 | }
21 |
22 | fun Activity.fadeInOut(): Bundle {
23 | return ActivityOptions.makeCustomAnimation(this, R.anim.fade_in, R.anim.fade_out).toBundle()
24 | }
25 |
26 | fun NavController.safeNavigate(action: NavDirections) {
27 | Timber.d("Current Destination: ${currentDestination}")
28 | currentDestination?.getAction(action.actionId)?.run {
29 | navigate(action)
30 | }
31 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/BaseDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation
2 |
3 | import android.graphics.Color
4 | import android.graphics.drawable.ColorDrawable
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.view.Window
10 | import androidx.databinding.DataBindingUtil
11 | import androidx.databinding.ViewDataBinding
12 | import androidx.fragment.app.DialogFragment
13 |
14 | abstract class BaseDialogFragment(private val layoutId: Int) : DialogFragment() {
15 |
16 | private var _binding: T? = null
17 | protected val binding
18 | get() = requireNotNull(_binding)
19 |
20 | override fun onCreateView(
21 | inflater: LayoutInflater,
22 | container: ViewGroup?,
23 | savedInstanceState: Bundle?
24 | ): View? {
25 | dialog?.window?.run {
26 | setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
27 | requestFeature(Window.FEATURE_NO_TITLE)
28 | }
29 |
30 | _binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
31 | binding.lifecycleOwner = viewLifecycleOwner
32 | return binding.root
33 | }
34 |
35 | override fun onDestroyView() {
36 | super.onDestroyView()
37 | _binding = null
38 | }
39 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.activity.OnBackPressedCallback
8 | import androidx.databinding.DataBindingUtil
9 | import androidx.databinding.ViewDataBinding
10 | import androidx.fragment.app.Fragment
11 | import androidx.lifecycle.Lifecycle
12 | import androidx.lifecycle.lifecycleScope
13 | import androidx.lifecycle.repeatOnLifecycle
14 | import com.google.android.material.snackbar.Snackbar
15 | import kotlinx.coroutines.flow.Flow
16 | import kotlinx.coroutines.flow.collectLatest
17 | import kotlinx.coroutines.launch
18 |
19 | abstract class BaseFragment(private val layoutId: Int) : Fragment() {
20 |
21 | private var _binding: T? = null
22 | protected val binding
23 | get() = requireNotNull(_binding)
24 |
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | _binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
31 | binding.lifecycleOwner = viewLifecycleOwner
32 | return binding.root
33 | }
34 |
35 |
36 | override fun onDestroyView() {
37 | super.onDestroyView()
38 | _binding = null
39 | }
40 |
41 | fun collectLatestFlow(flow: Flow, action: suspend (T) -> Unit) {
42 | viewLifecycleOwner.lifecycleScope.launch {
43 | viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
44 | flow.collectLatest(action)
45 | }
46 | }
47 | }
48 |
49 | fun showSnackBar(messageId: Int) {
50 | Snackbar.make(this.requireView(), messageId, Snackbar.LENGTH_LONG).show()
51 | }
52 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/CustomNaverMap.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation
2 |
3 | import com.naver.maps.geometry.LatLng
4 | import com.naver.maps.geometry.LatLngBounds
5 | import com.naver.maps.map.LocationTrackingMode
6 | import com.naver.maps.map.NaverMap
7 | import com.naver.maps.map.overlay.OverlayImage
8 | import com.naver.maps.map.util.FusedLocationSource
9 | import com.rpg.funbox.R
10 |
11 | object CustomNaverMap {
12 |
13 | private const val MIN_ZOOM = 5.0
14 | private const val MAX_ZOOM = 20.0
15 | private const val SOUTH_WEST_LATITUDE = 31.43
16 | private const val SOUTH_WEST_LONGITUDE = 122.37
17 | private const val NORTH_EAST_LATITUDE = 44.35
18 | private const val NORTH_EAST_LONGITUDE = 132.0
19 |
20 | fun setNaverMap(naverMap: NaverMap, fusedLocationSource: FusedLocationSource): NaverMap {
21 | naverMap.locationSource = fusedLocationSource
22 | naverMap.locationTrackingMode = LocationTrackingMode.Face
23 | naverMap.uiSettings.isLocationButtonEnabled = true
24 |
25 | naverMap.minZoom = MIN_ZOOM
26 | naverMap.maxZoom = MAX_ZOOM
27 | naverMap.uiSettings.isZoomControlEnabled = true
28 |
29 | val locationOverlay = naverMap.locationOverlay
30 | locationOverlay.isVisible = true
31 | locationOverlay.icon = OverlayImage.fromResource(R.drawable.my_location)
32 |
33 | naverMap.extent = LatLngBounds(
34 | LatLng(SOUTH_WEST_LATITUDE, SOUTH_WEST_LONGITUDE), LatLng(
35 | NORTH_EAST_LATITUDE, NORTH_EAST_LONGITUDE
36 | )
37 | )
38 |
39 | return naverMap
40 | }
41 |
42 | fun setNaverMapLocationOverlay(naverMap: NaverMap): NaverMap {
43 | naverMap.locationOverlay.apply {
44 | isVisible = true
45 | iconHeight = 120
46 | iconWidth = 120
47 | icon = OverlayImage.fromResource(R.drawable.navi_icon)
48 | }
49 |
50 | return naverMap
51 | }
52 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/MapSocket.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation
2 |
3 | import com.google.gson.Gson
4 | import com.rpg.funbox.presentation.map.ApplyGameToServerData
5 | import com.rpg.funbox.presentation.map.Chat
6 | import com.rpg.funbox.presentation.map.GameApplyAnswerToServerData
7 | import com.rpg.funbox.presentation.map.QuizAnswerToServer
8 | import com.rpg.funbox.presentation.map.SocketApplication
9 | import com.rpg.funbox.presentation.map.VerifyAnswerToServer
10 | import org.json.JSONObject
11 | import timber.log.Timber
12 |
13 | object MapSocket {
14 | val mSocket = SocketApplication.get()
15 |
16 | fun verifyAnswer(roomId: String, isCorrect: Boolean) {
17 | val json = Gson().toJson(VerifyAnswerToServer(isCorrect, roomId))
18 | mSocket.emit("verifyAnswer", JSONObject(json))
19 | Timber.d(roomId)
20 | }
21 |
22 | fun sendQuizAnswer(roomId: String, answer: String) {
23 | val json = Gson().toJson(QuizAnswerToServer(answer, roomId))
24 | mSocket.emit("quizAnswer", JSONObject(json))
25 | Timber.d("Room: $roomId, Answer: $answer")
26 | }
27 |
28 | fun acceptGame(roomId: String) {
29 | val json = Gson().toJson(GameApplyAnswerToServerData(roomId, "ACCEPT"))
30 | mSocket.emit("gameApplyAnswer", JSONObject(json))
31 | }
32 |
33 | fun rejectGame(roomId: String) {
34 | val json = Gson().toJson(GameApplyAnswerToServerData(roomId, "REJECT"))
35 | mSocket.emit("gameApplyAnswer", JSONObject(json))
36 | }
37 |
38 | fun applyGame(id: Int) {
39 | val json = Gson().toJson(ApplyGameToServerData(id))
40 | mSocket.emit("gameApply", JSONObject(json))
41 | Timber.d("Other Id: $id")
42 | }
43 |
44 | fun quitGame() {
45 | mSocket.emit("quitGame")
46 | }
47 |
48 | fun send(otherId:Int, message:String){
49 | val json = Gson().toJson(Chat(otherId,message))
50 | Timber.d("Other Id: $otherId $message")
51 | mSocket.emit("directMessage", JSONObject(json))
52 | }
53 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/GameActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 | import androidx.activity.viewModels
6 | import com.rpg.funbox.app.MainApplication
7 | import com.rpg.funbox.data.JwtDecoder
8 | import com.rpg.funbox.databinding.ActivityGameBinding
9 | import com.rpg.funbox.presentation.MapSocket
10 | import com.rpg.funbox.presentation.game.quiz.QuizViewModel
11 |
12 | class GameActivity : AppCompatActivity() {
13 |
14 | private lateinit var binding: ActivityGameBinding
15 | private val viewModel: QuizViewModel by viewModels()
16 |
17 | private val myUserId =
18 | JwtDecoder.getUser(MainApplication.mySharedPreferences.getJWT("jwt", "")).id
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | binding = ActivityGameBinding.inflate(layoutInflater)
22 | setContentView(binding.root)
23 | binding.vm = viewModel
24 | binding.lifecycleOwner = this
25 |
26 | viewModel.connectSocket(myUserId = myUserId)
27 |
28 | initUsersState()
29 | }
30 |
31 | private fun initUsersState() {
32 | viewModel.setRoomId(intent.getStringExtra("RoomId"))
33 | viewModel.setUserState(intent.getBooleanExtra("StartGame",false))
34 | viewModel.setUsersInfo(intent.getIntExtra("OtherUserId", -1))
35 |
36 | if (!viewModel.userState.value) {
37 | viewModel.roomId.value?.let { MapSocket.acceptGame(it) }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/GameSelectBindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | import android.view.View
4 | import android.widget.AdapterView
5 | import android.widget.ArrayAdapter
6 | import android.widget.Spinner
7 | import android.widget.TextView
8 | import androidx.databinding.BindingAdapter
9 | import androidx.databinding.InverseBindingAdapter
10 | import androidx.databinding.InverseBindingListener
11 | import androidx.recyclerview.widget.ListAdapter
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.rpg.funbox.R
14 | import com.rpg.funbox.presentation.game.gameselect.GameSelectUiState.Companion.QUESTION_CARD_TYPE
15 | import timber.log.Timber
16 |
17 | @BindingAdapter("app:game_title")
18 | fun TextView.bindGameTitle(gameCard: GameSelectUiState.GameCard) {
19 | when (gameCard.gameType) {
20 | QUESTION_CARD_TYPE -> {
21 | text = resources.getString(R.string.game_question_card_title)
22 | }
23 | }
24 | }
25 |
26 | @BindingAdapter("app:entries")
27 | fun Spinner.setEntries(entries: List?) {
28 | entries?.run {
29 | val arrayAdapter = ArrayAdapter(context, R.layout.quiz_question_count_spinner, entries)
30 | arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
31 | adapter = arrayAdapter
32 | }
33 | }
34 |
35 | @InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged")
36 | fun Spinner.getSelectedValue(): Any? {
37 | return selectedItem
38 | }
39 |
40 | @BindingAdapter("selectedValueAttrChanged")
41 | fun Spinner.setInverseBindingAdapter(inverseBindingListener: InverseBindingListener?) {
42 | inverseBindingListener?.run {
43 | onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
44 | override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
45 | if (tag != position) {
46 | inverseBindingListener.onChange()
47 | }
48 | }
49 |
50 | override fun onNothingSelected(parent: AdapterView<*>?) {}
51 | }
52 | }
53 | }
54 |
55 | @BindingAdapter("selectedValue")
56 | fun Spinner.setSelectedValue(selectedValue: Int) {
57 | adapter?.run {
58 | Timber.d("Select $selectedValue")
59 | val position = (adapter as ArrayAdapter).getPosition(selectedValue)
60 | setSelection(position, false)
61 | tag = position
62 | }
63 | }
64 |
65 | @BindingAdapter("app:submitGames")
66 | fun RecyclerView.bindItems(items: List) {
67 | val adapter = this.adapter ?: return
68 | val listAdapter: ListAdapter = adapter as ListAdapter
69 | listAdapter.submitList(items)
70 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/GameSelectFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.viewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import androidx.navigation.fragment.findNavController
8 | import com.rpg.funbox.R
9 | import com.rpg.funbox.databinding.FragmentGameSelectBinding
10 | import com.rpg.funbox.presentation.BaseFragment
11 | import kotlinx.coroutines.launch
12 | import timber.log.Timber
13 |
14 | class GameSelectFragment : BaseFragment(R.layout.fragment_game_select) {
15 |
16 | private val viewModel: GameSelectViewModel by viewModels()
17 | private val viewModel2: QuestionGameViewModel by viewModels()
18 | private val entry = listOf(1, 5, 10, 20)
19 |
20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
21 | super.onViewCreated(view, savedInstanceState)
22 |
23 | binding.vm = viewModel
24 | binding.gameListAdapter = GameListAdapter(viewModel)
25 |
26 | lifecycleScope.launch {
27 | viewModel2.quizQuestionCount.collect {
28 | Timber.d("Count: $it")
29 | }
30 | }
31 |
32 | collectLatestFlow(viewModel.gameSelectUiEvent) { handleUiEvent(it) }
33 | }
34 |
35 | private fun handleUiEvent(event: GameSelectUiEvent) = when (event) {
36 | is GameSelectUiEvent.NetworkErrorEvent -> {
37 | showSnackBar(R.string.network_error_message)
38 | }
39 |
40 | is GameSelectUiEvent.GameSelectSuccess -> {
41 | Timber.d("Question Count: ${viewModel2.quizQuestionCount.value}")
42 | // findNavController().navigate(R.id.action_GameSelectFragment_to_WaitFragment)
43 | }
44 |
45 | is GameSelectUiEvent.GameListSubmit -> {
46 | viewModel2.setSpinnerEntry(entry)
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/GameSelectUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | sealed class GameSelectUiEvent {
4 |
5 | data class NetworkErrorEvent(val message: String = "Network Error") : GameSelectUiEvent()
6 | data object GameSelectSuccess : GameSelectUiEvent()
7 | data object GameListSubmit : GameSelectUiEvent()
8 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/GameSelectUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | import java.util.UUID
4 |
5 | sealed class GameSelectUiState(val id: String = UUID.randomUUID().toString()) {
6 |
7 | data class GameCard(
8 | val gameType: Int,
9 | val questionCountList: List = emptyList()
10 | ) : GameSelectUiState()
11 |
12 | data class ToBeDetermined(
13 | val gameType: Int = TO_BE_DETERMINED
14 | ) : GameSelectUiState()
15 |
16 | companion object {
17 | const val TO_BE_DETERMINED = 0
18 | const val QUESTION_CARD_TYPE = 1
19 | const val OTHER_GAMES = 2
20 | }
21 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/GameSelectViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.rpg.funbox.presentation.game.gameselect.GameSelectUiState.Companion.QUESTION_CARD_TYPE
6 | import kotlinx.coroutines.flow.MutableSharedFlow
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 | import kotlinx.coroutines.flow.asSharedFlow
9 | import kotlinx.coroutines.flow.asStateFlow
10 | import kotlinx.coroutines.launch
11 |
12 | class GameSelectViewModel : ViewModel(), OnGameClickListener {
13 |
14 | private val _gameSelectUiEvent = MutableSharedFlow()
15 | val gameSelectUiEvent = _gameSelectUiEvent.asSharedFlow()
16 |
17 | private val _gameList = MutableStateFlow>(listOf())
18 | val gameList = _gameList.asStateFlow()
19 |
20 | init {
21 | addGameContent()
22 | }
23 |
24 | override fun onClick(game: GameSelectUiState.GameCard) {
25 | viewModelScope.launch {
26 | when (game.gameType) {
27 | QUESTION_CARD_TYPE -> {
28 | _gameSelectUiEvent.emit(GameSelectUiEvent.GameSelectSuccess)
29 | }
30 | }
31 | }
32 | }
33 |
34 | private fun addGameContent() {
35 | _gameList.value += getQuestionCard()
36 | _gameList.value += getToBeDetermined()
37 | viewModelScope.launch {
38 | _gameSelectUiEvent.emit(GameSelectUiEvent.GameListSubmit)
39 | }
40 | }
41 |
42 | private fun getQuestionCard(): GameSelectUiState.GameCard {
43 | return GameSelectUiState.GameCard(
44 | gameType = QUESTION_CARD_TYPE,
45 | questionCountList = listOf(1, 5, 10, 20)
46 | )
47 | }
48 |
49 | private fun getToBeDetermined(): GameSelectUiState.ToBeDetermined {
50 | return GameSelectUiState.ToBeDetermined()
51 | }
52 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/OnGameClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | interface OnGameClickListener {
4 | fun onClick(game: GameSelectUiState.GameCard)
5 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/gameselect/QuestionGameViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.gameselect
2 |
3 | import androidx.lifecycle.ViewModel
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import kotlinx.coroutines.flow.StateFlow
6 |
7 | class QuestionGameViewModel : ViewModel() {
8 |
9 | private val _spinnerEntry = MutableStateFlow(emptyList())
10 | val spinnerEntry : StateFlow?> = _spinnerEntry
11 |
12 | val quizQuestionCount = MutableStateFlow(0)
13 |
14 | fun setSpinnerEntry(entry: List) {
15 | _spinnerEntry.value = entry
16 | }
17 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/AnswerCheckFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.FragmentAnswerCheckBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import kotlinx.coroutines.flow.collectLatest
11 | import kotlinx.coroutines.launch
12 |
13 | class AnswerCheckFragment : BaseDialogFragment(R.layout.fragment_answer_check) {
14 |
15 | private val viewModel: QuizViewModel by activityViewModels()
16 |
17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
18 | super.onViewCreated(view, savedInstanceState)
19 | binding.vm = viewModel
20 |
21 | isCancelable = false
22 |
23 | lifecycleScope.launch {
24 | viewModel.quizUiEvent.collectLatest { uiEvent ->
25 | when (uiEvent) {
26 | QuizUiEvent.QuizAnswerCheckRight -> dismiss()
27 | QuizUiEvent.QuizAnswerCheckWrong -> dismiss()
28 | else -> {}
29 | }
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/ChatAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.DiffUtil
6 | import androidx.recyclerview.widget.ListAdapter
7 | import androidx.recyclerview.widget.RecyclerView
8 | import androidx.recyclerview.widget.RecyclerView.ViewHolder
9 | import androidx.viewbinding.ViewBinding
10 | import com.rpg.funbox.databinding.ItemChatBinding
11 | import com.rpg.funbox.databinding.ItemChatOtherBinding
12 |
13 | class ChatAdapter : ListAdapter(diffUtil) {
14 | class ChatViewHolder(private val binding: ItemChatBinding) :
15 | ViewHolder(binding.root) {
16 | fun bind(item: MessageItem) {
17 | binding.tvChatText.text = item.message
18 | }
19 | }
20 |
21 | class OtherChatViewHolder(private val binding: ItemChatOtherBinding) :
22 | ViewHolder(binding.root) {
23 | fun bind(item: MessageItem) {
24 | binding.tvChatText.text = item.message
25 | }
26 | }
27 |
28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
29 | return if (viewType == MY_CHAT) {
30 | ChatViewHolder(
31 | ItemChatBinding.inflate(
32 | LayoutInflater.from(parent.context),
33 | parent,
34 | false
35 | )
36 | )
37 | } else {
38 | OtherChatViewHolder(
39 | ItemChatOtherBinding.inflate(
40 | LayoutInflater.from(parent.context),
41 | parent,
42 | false
43 | )
44 | )
45 | }
46 | }
47 |
48 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
49 | if (getItemViewType(position) == MY_CHAT) {
50 | (holder as ChatViewHolder).bind(currentList[position])
51 | } else {
52 | (holder as OtherChatViewHolder).bind(currentList[position])
53 | }
54 | }
55 |
56 | override fun getItemViewType(position: Int): Int {
57 | return if (currentList[position].type == 0) {
58 | MY_CHAT
59 | } else {
60 | OTHER_CHAT
61 | }
62 | }
63 |
64 | companion object {
65 | val diffUtil = object : DiffUtil.ItemCallback() {
66 | override fun areItemsTheSame(oldItem: MessageItem, newItem: MessageItem): Boolean {
67 | return oldItem == newItem
68 | }
69 |
70 | override fun areContentsTheSame(oldItem: MessageItem, newItem: MessageItem): Boolean {
71 | return false
72 | }
73 | }
74 |
75 |
76 | private const val MY_CHAT = 0
77 | private const val OTHER_CHAT = 1
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/LoadingDialog.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import androidx.navigation.fragment.findNavController
8 | import com.rpg.funbox.R
9 | import com.rpg.funbox.databinding.DialogLoadingBinding
10 | import com.rpg.funbox.presentation.BaseDialogFragment
11 | import kotlinx.coroutines.flow.collect
12 | import kotlinx.coroutines.flow.collectLatest
13 | import kotlinx.coroutines.launch
14 | import timber.log.Timber
15 |
16 | class LoadingDialog : BaseDialogFragment(R.layout.dialog_loading) {
17 |
18 | private val viewModel: QuizViewModel by activityViewModels()
19 |
20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
21 | super.onViewCreated(view, savedInstanceState)
22 | binding.vm = viewModel
23 |
24 | isCancelable = false
25 |
26 | lifecycleScope.launch {
27 | viewModel.quizUiState.collect {
28 | Timber.d(viewModel.quizUiState.value.isUserQuizState.toString())
29 | if (it.isUserQuizState) {
30 | dismiss()
31 | }
32 | }
33 | }
34 |
35 | lifecycleScope.launch {
36 | viewModel.quizUiEvent.collectLatest { uiEvent ->
37 | if (uiEvent == QuizUiEvent.QuizScoreBoard) {
38 | findNavController().navigate(R.id.action_loadingDialog_to_scoreBoardFragment)
39 | }
40 | }
41 | }
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/MessageItem.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | data class MessageItem(
4 | val type:Int,
5 | val message:String
6 | )
7 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/NetworkAlertFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.FragmentNetworkAlertBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import kotlinx.coroutines.flow.collectLatest
11 | import kotlinx.coroutines.launch
12 |
13 | class NetworkAlertFragment : BaseDialogFragment(R.layout.fragment_network_alert) {
14 |
15 | private val viewModel: QuizViewModel by activityViewModels()
16 |
17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
18 | super.onViewCreated(view, savedInstanceState)
19 | binding.vm = viewModel
20 |
21 | isCancelable = false
22 |
23 | lifecycleScope.launch {
24 | viewModel.quizUiEvent.collectLatest {
25 | if (it == QuizUiEvent.QuizFinish) {
26 | requireActivity().finish()
27 | }
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/QuizBindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | import android.widget.EditText
4 | import android.widget.ImageView
5 | import android.widget.TextView
6 | import androidx.core.net.toUri
7 | import androidx.databinding.BindingAdapter
8 | import coil.load
9 | import com.rpg.funbox.R
10 |
11 | @BindingAdapter("app:setImage")
12 | fun ImageView.bindImage(url: String?) {
13 | if (url == null) {
14 | load(R.drawable.profile_none)
15 | } else {
16 | load("https://kr.object.ncloudstorage.com/funbox-profiles/${url}".toUri())
17 | }
18 | }
19 |
20 | @BindingAdapter("app:question_marquee")
21 | fun TextView.bindMarquee(quizUiState: QuizUiState) {
22 | isSelected = true
23 | }
24 |
25 | @BindingAdapter("app:delete_text")
26 | fun EditText.deleteText(quizUiState: QuizUiState) {
27 | if (!quizUiState.isEtEnable) {
28 | text = null
29 | }
30 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/QuizUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | sealed class QuizUiEvent {
4 |
5 | data class NetworkErrorEvent(val message: String = "네트워크에 문제가 있습니다.") : QuizUiEvent()
6 |
7 | data object WaitSuccess : QuizUiEvent()
8 |
9 | data object QuizAnswerSubmit : QuizUiEvent()
10 |
11 | data object QuizNetworkDisconnected : QuizUiEvent()
12 |
13 | data object QuizAnswerCheckStart : QuizUiEvent()
14 |
15 | data object QuizAnswerCheckRight : QuizUiEvent()
16 |
17 | data object QuizAnswerCheckWrong : QuizUiEvent()
18 |
19 | data object QuizScoreBoard : QuizUiEvent()
20 |
21 | data object QuizFinish : QuizUiEvent()
22 |
23 | data object SendMessage : QuizUiEvent()
24 |
25 | data object ReceiveMessage : QuizUiEvent()
26 |
27 | data object OtherPlaying : QuizUiEvent()
28 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/QuizUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | data class QuizUiState(
4 | val waiting: Boolean = true,
5 | val finishWaiting: Boolean = false,
6 | val answerValidState: Boolean = false,
7 | val answerWriteState: Boolean = true,
8 | val userQuizState: UserQuizState = UserQuizState.Answer
9 | ) {
10 | val isAnswerSubmitBtnEnable: Boolean = (answerValidState)
11 | val isEtEnable: Boolean = (answerWriteState)
12 | val isUserQuizState: Boolean = (userQuizState == UserQuizState.Quiz)
13 | }
14 |
15 | enum class UserQuizState {
16 | Quiz, Answer
17 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/quiz/ScoreBoardFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.quiz
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.FragmentScoreBoardBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import kotlinx.coroutines.flow.collectLatest
11 | import kotlinx.coroutines.launch
12 |
13 | class ScoreBoardFragment : BaseDialogFragment(R.layout.fragment_score_board) {
14 |
15 | private val viewModel: QuizViewModel by activityViewModels()
16 |
17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
18 | super.onViewCreated(view, savedInstanceState)
19 | binding.vm = viewModel
20 |
21 | isCancelable = false
22 |
23 | lifecycleScope.launch {
24 | viewModel.quizUiEvent.collectLatest {
25 | if (it == QuizUiEvent.QuizFinish) {
26 | requireActivity().finish()
27 | }
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/game/wait/WaitFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.game.wait
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.View
6 | import android.widget.Toast
7 | import androidx.activity.OnBackPressedCallback
8 | import androidx.fragment.app.activityViewModels
9 | import androidx.navigation.fragment.findNavController
10 | import com.rpg.funbox.R
11 | import com.rpg.funbox.databinding.FragmentWaitBinding
12 | import com.rpg.funbox.presentation.BaseFragment
13 | import com.rpg.funbox.presentation.MapSocket
14 | import com.rpg.funbox.presentation.game.quiz.QuizUiEvent
15 | import com.rpg.funbox.presentation.game.quiz.QuizViewModel
16 |
17 | class WaitFragment : BaseFragment(R.layout.fragment_wait) {
18 |
19 | private val viewModel: QuizViewModel by activityViewModels()
20 | private lateinit var backPressedCallback: OnBackPressedCallback
21 | private var backPressTime: Long = 0
22 |
23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | super.onViewCreated(view, savedInstanceState)
25 | binding.vm = viewModel
26 |
27 | collectLatestFlow(viewModel.quizUiEvent) { handleUiEvent(it) }
28 |
29 | if (!viewModel.userState.value) {
30 | findNavController().navigate(R.id.action_WaitFragment_to_QuizFragment)
31 | }
32 | }
33 |
34 | override fun onAttach(context: Context) {
35 | super.onAttach(context)
36 |
37 | setBackPressedCallback()
38 | }
39 |
40 | private fun handleUiEvent(event: QuizUiEvent) = when (event) {
41 | is QuizUiEvent.NetworkErrorEvent -> {
42 | showSnackBar(R.string.network_error_message)
43 | requireActivity().finish()
44 | }
45 |
46 | is QuizUiEvent.WaitSuccess -> {
47 | findNavController().navigate(R.id.action_WaitFragment_to_QuizFragment)
48 | }
49 |
50 | is QuizUiEvent.QuizFinish -> {
51 | requireActivity().finish()
52 | }
53 |
54 | is QuizUiEvent.OtherPlaying -> {
55 | showSnackBar(R.string.other_not)
56 | requireActivity().finish()
57 | }
58 |
59 | else -> {}
60 | }
61 |
62 | private fun setBackPressedCallback() {
63 | backPressedCallback = object : OnBackPressedCallback(true) {
64 | override fun handleOnBackPressed() {
65 | if (backPressTime + 3000 > System.currentTimeMillis()) {
66 | MapSocket.quitGame()
67 | requireActivity().finish()
68 | } else {
69 | Toast.makeText(requireContext(), resources.getString(R.string.finish_quiz_toast_message), Toast.LENGTH_LONG).show()
70 | backPressTime = System.currentTimeMillis()
71 | }
72 | }
73 | }
74 | requireActivity().onBackPressedDispatcher.addCallback(
75 | this,
76 | backPressedCallback
77 | )
78 | }
79 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/AccessPermission.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login
2 |
3 | import android.Manifest
4 |
5 | object AccessPermission {
6 | const val LOCATION_PERMISSION_REQUEST_CODE = 5000
7 |
8 | val profilePermissionList = arrayOf(
9 | Manifest.permission.CAMERA,
10 | Manifest.permission.WRITE_EXTERNAL_STORAGE
11 | )
12 |
13 | val locationPermissionList = arrayOf(
14 | Manifest.permission.ACCESS_FINE_LOCATION,
15 | Manifest.permission.ACCESS_COARSE_LOCATION
16 | )
17 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/TitleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login
2 |
3 | import android.graphics.Rect
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import android.view.MotionEvent
7 | import android.view.View
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.EditText
10 | import androidx.activity.viewModels
11 | import com.rpg.funbox.databinding.ActivityTitleBinding
12 | import com.rpg.funbox.presentation.login.title.TitleViewModel
13 |
14 | class TitleActivity : AppCompatActivity() {
15 |
16 | private lateinit var binding: ActivityTitleBinding
17 | private val viewModel: TitleViewModel by viewModels()
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | binding = ActivityTitleBinding.inflate(layoutInflater)
22 | setContentView(binding.root)
23 |
24 | binding.vm = viewModel
25 | }
26 |
27 | override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
28 | if (event?.action != MotionEvent.ACTION_DOWN) {
29 | return super.dispatchTouchEvent(event)
30 | }
31 |
32 | if (this.currentFocus is EditText) {
33 | val outRect = Rect()
34 | this.currentFocus?.let {
35 | it.getGlobalVisibleRect(outRect)
36 | hideKeyboard(outRect, event, it)
37 | }
38 | }
39 |
40 | return super.dispatchTouchEvent(event)
41 | }
42 |
43 | private fun hideKeyboard(
44 | outRect: Rect,
45 | event: MotionEvent,
46 | it: View
47 | ) {
48 | if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
49 | it.clearFocus()
50 |
51 | val inputMethodManager: InputMethodManager =
52 | this.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
53 | inputMethodManager.hideSoftInputFromWindow(
54 | it.windowToken,
55 | InputMethodManager.HIDE_NOT_ALWAYS
56 | )
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/nickname/NicknameFragment.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.nickname
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.navigation.fragment.findNavController
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.FragmentNicknameBinding
9 | import com.rpg.funbox.presentation.BaseFragment
10 | import com.rpg.funbox.presentation.login.title.TitleViewModel
11 |
12 | class NicknameFragment : BaseFragment(R.layout.fragment_nickname) {
13 |
14 | private val viewModel: TitleViewModel by activityViewModels()
15 |
16 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
17 | super.onViewCreated(view, savedInstanceState)
18 | binding.vm = viewModel
19 |
20 | collectLatestFlow(viewModel.nicknameUiEvent) { handleUiEvent(it) }
21 | }
22 |
23 | private fun handleUiEvent(event: NicknameUiEvent) = when (event) {
24 | is NicknameUiEvent.NicknameSubmit -> {
25 | findNavController().navigate(R.id.action_NicknameFragment_to_ProfileFragment)
26 | }
27 |
28 | is NicknameUiEvent.NetworkErrorEvent -> {
29 | showSnackBar(R.string.network_error_message)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/nickname/NicknameUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.nickname
2 |
3 | sealed class NicknameUiEvent {
4 | data class NetworkErrorEvent(val message: String = "Network Error") : NicknameUiEvent()
5 |
6 | data object NicknameSubmit : NicknameUiEvent()
7 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/nickname/NicknameUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.nickname
2 |
3 | data class NicknameUiState(
4 | val nicknameValidState: NicknameValidState = NicknameValidState.None
5 | ) {
6 | val isNextBtnEnable: Boolean = (nicknameValidState == NicknameValidState.Valid)
7 | }
8 |
9 | enum class NicknameValidState {
10 | None, Valid
11 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/profile/ProfileBindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.profile
2 |
3 | import android.net.Uri
4 | import android.widget.ImageButton
5 | import androidx.databinding.BindingAdapter
6 | import coil.load
7 | import coil.size.Scale
8 | import coil.transform.RoundedCornersTransformation
9 | import com.rpg.funbox.R
10 |
11 | @BindingAdapter("app:profile_image_uri")
12 | fun ImageButton.bindImageUri(uri: Uri?) {
13 | if (uri == null) {
14 | setImageResource(R.drawable.group_1940)
15 | } else {
16 | load(uri) {
17 | scale(Scale.FILL)
18 | crossfade(enable = true)
19 | transformations(RoundedCornersTransformation(10F))
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/profile/ProfileUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.profile
2 |
3 | sealed class ProfileUiEvent {
4 |
5 | data class NetworkErrorEvent(val message: String = "Network Error") : ProfileUiEvent()
6 | data object ProfileSubmit : ProfileUiEvent()
7 | data object ProfileSelect : ProfileUiEvent()
8 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/profile/ProfileUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.profile
2 |
3 | data class ProfileUiState(
4 | val profileValidState: ProfileValidState = ProfileValidState.None
5 | ) {
6 | val isBtnProfileEnable: Boolean = (profileValidState == ProfileValidState.Valid)
7 | }
8 |
9 | enum class ProfileValidState {
10 | None, Valid
11 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/splash/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.splash
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Intent
5 | import androidx.appcompat.app.AppCompatActivity
6 | import android.os.Bundle
7 | import androidx.activity.viewModels
8 | import androidx.lifecycle.lifecycleScope
9 | import com.rpg.funbox.app.MainApplication
10 | import com.rpg.funbox.presentation.MainActivity
11 | import com.rpg.funbox.presentation.login.TitleActivity
12 | import kotlinx.coroutines.flow.collectLatest
13 | import kotlinx.coroutines.launch
14 | import timber.log.Timber
15 |
16 | @SuppressLint("CustomSplashScreen")
17 | class SplashActivity : AppCompatActivity() {
18 |
19 | private val viewModel: SplashViewModel by viewModels()
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 |
24 | // MainApplication.mySharedPreferences.setJWT("jwt", "")
25 |
26 | collectLatestUiEvent()
27 | }
28 |
29 | private fun collectLatestUiEvent() {
30 | lifecycleScope.launch {
31 | viewModel.getUsersLocations(0.0, 0.0)
32 | viewModel.splashUiEvent.collectLatest { splashUiEvent ->
33 | when (splashUiEvent) {
34 | is SplashUiEvent.NetworkErrorEvent -> {
35 | val intent = Intent(this@SplashActivity, TitleActivity::class.java)
36 | intent.putExtra("ServerError", true)
37 | Timber.d("NetworkError")
38 | startActivity(intent)
39 | }
40 |
41 | is SplashUiEvent.Unauthorized -> {
42 | val intent = Intent(this@SplashActivity, TitleActivity::class.java)
43 | intent.putExtra("ServerError", false)
44 | startActivity(intent)
45 | }
46 |
47 | is SplashUiEvent.GetUsersLocationsSuccess -> {
48 | startActivity(Intent(this@SplashActivity, MainActivity::class.java))
49 | }
50 | }
51 | this@SplashActivity.finish()
52 | }
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/splash/SplashUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.splash
2 |
3 | sealed class SplashUiEvent {
4 |
5 | data class NetworkErrorEvent(val message: String = "Network Error") : SplashUiEvent()
6 |
7 | data object GetUsersLocationsSuccess : SplashUiEvent()
8 |
9 | data object Unauthorized : SplashUiEvent()
10 | }
11 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/splash/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.splash
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.rpg.funbox.data.repository.UserRepository
6 | import com.rpg.funbox.data.repository.UserRepositoryImpl
7 | import com.rpg.funbox.data.repository.UsersLocationRepository
8 | import com.rpg.funbox.data.repository.UsersLocationRepositoryImpl
9 | import kotlinx.coroutines.flow.MutableSharedFlow
10 | import kotlinx.coroutines.flow.asSharedFlow
11 | import kotlinx.coroutines.launch
12 | import timber.log.Timber
13 |
14 | class SplashViewModel : ViewModel() {
15 |
16 | private val usersLocationRepository: UsersLocationRepository = UsersLocationRepositoryImpl()
17 | private val userRepository: UserRepository = UserRepositoryImpl()
18 |
19 | private val _splashUiEvent = MutableSharedFlow()
20 | val splashUiEvent = _splashUiEvent.asSharedFlow()
21 |
22 | private fun validateUserName(userName: String?): Boolean {
23 | return (userName != null)
24 | }
25 |
26 | fun getUsersLocations(locX: Double, locY: Double) {
27 | viewModelScope.launch {
28 | try {
29 | usersLocationRepository.getUsersLocation(locX, locY)?.let { userLocationResponse ->
30 | when (userLocationResponse.resultMessage) {
31 | "OK" -> {
32 | if (validateUserName(userRepository.getUserInfo()?.userName)) {
33 | _splashUiEvent.emit(SplashUiEvent.GetUsersLocationsSuccess)
34 | Timber.d("OK")
35 | } else {
36 | _splashUiEvent.emit(SplashUiEvent.Unauthorized)
37 | Timber.d("No Nickname")
38 | }
39 | }
40 |
41 | "Unauthorized" -> {
42 | _splashUiEvent.emit(SplashUiEvent.Unauthorized)
43 | Timber.d("Unauthorized")
44 | }
45 |
46 | else -> {
47 | _splashUiEvent.emit(SplashUiEvent.NetworkErrorEvent())
48 | Timber.d("else")
49 | }
50 | }
51 | }
52 | } catch (e: Exception) {
53 | _splashUiEvent.emit(SplashUiEvent.NetworkErrorEvent())
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/title/TitleBindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.title
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.res.ColorStateList
5 | import android.widget.ImageView
6 | import androidx.databinding.BindingAdapter
7 | import com.rpg.funbox.R
8 |
9 | @SuppressLint("ResourceAsColor")
10 | @BindingAdapter("app:image_tint")
11 | fun ImageView.bindTint(titleUiState: TitleUiState) {
12 | when (titleUiState.networkSuccess) {
13 | false -> {
14 | imageTintList = ColorStateList.valueOf(R.color.disabled_color)
15 | }
16 |
17 | else -> {}
18 | }
19 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/title/TitleUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.title
2 |
3 | sealed class TitleUiEvent {
4 |
5 | data class NetworkErrorEvent(val message: String = "Network Error") : TitleUiEvent()
6 |
7 | data object NaverLoginStart : TitleUiEvent()
8 |
9 | data object SignUpStart : TitleUiEvent()
10 |
11 | data object NaverLoginSuccess : TitleUiEvent()
12 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/login/title/TitleUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.login.title
2 |
3 | data class TitleUiState(
4 | val networkSuccess: Boolean = true
5 | ) {
6 | val isNaverLoginBtnEnabled: Boolean = (networkSuccess)
7 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/GameData.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | data class ApplyGameToServerData(
4 | val opponentId: Int
5 | )
6 |
7 | data class ApplyGameFromServerData(
8 | val userId: String,
9 | val roomId: String
10 | )
11 |
12 | data class GameApplyAnswerToServerData(
13 | val roomId: String,
14 | val answer: String,
15 | )
16 |
17 | data class GameApplyAnswerFromServerData(
18 | val answer: String,
19 | )
20 |
21 | data class QuizFromServer(
22 | val roomId: String,
23 | val quiz: String,
24 | val target: Int
25 | )
26 |
27 | data class QuizAnswerToServer(
28 | val answer: String,
29 | val roomId: String
30 | )
31 |
32 | data class QuizAnswerFromServer(
33 | val answer: String,
34 | val roomId: String
35 | )
36 |
37 | data class VerifyAnswerToServer(
38 | val isCorrect: Boolean,
39 | val roomId: String
40 | )
41 |
42 | class ScoreFromServer : ArrayList()
43 |
44 | data class ScoreFromServerItem(
45 | val id: Int,
46 | val score: Int,
47 | val username: String
48 | )
49 |
50 | data class Chat(
51 | val userId: Int,
52 | val message: String
53 | )
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/GetGameDialog.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.DialogGetGameBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import com.rpg.funbox.presentation.setting.SettingViewModel
11 | import kotlinx.coroutines.flow.collectLatest
12 | import kotlinx.coroutines.launch
13 |
14 | class GetGameDialog : BaseDialogFragment(R.layout.dialog_get_game) {
15 |
16 | private val viewModel: MapViewModel by activityViewModels()
17 | private val settingViewModel: SettingViewModel by activityViewModels()
18 |
19 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
20 | super.onViewCreated(view, savedInstanceState)
21 | binding.vm = viewModel
22 |
23 | isCancelable = false
24 |
25 | lifecycleScope.launch {
26 | viewModel.mapUiEvent.collectLatest { uiEvent ->
27 | if (uiEvent == MapUiEvent.CancelGame) dismiss()
28 | }
29 | }
30 |
31 | binding.btnOk.setOnClickListener {
32 | viewModel.toGame()
33 | settingViewModel.toGame()
34 | dismiss()
35 | }
36 |
37 | binding.btnNo.setOnClickListener{
38 | viewModel.rejectGame()
39 | settingViewModel.rejectGame()
40 | dismiss()
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/MapBindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | class MapBindingAdapters {
4 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/MapProfile.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.os.Build
6 | import android.util.AttributeSet
7 | import android.view.LayoutInflater
8 | import androidx.annotation.RequiresApi
9 | import androidx.constraintlayout.widget.ConstraintLayout
10 | import com.rpg.funbox.databinding.MapProfileBinding
11 | import kotlinx.coroutines.GlobalScope
12 | import kotlinx.coroutines.launch
13 |
14 | class MapProfile @JvmOverloads constructor(
15 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
16 | ) : ConstraintLayout(context, attrs, defStyleAttr) {
17 | private val layoutInflater = LayoutInflater.from(context)
18 | private val binding: MapProfileBinding = MapProfileBinding.inflate(layoutInflater, this, true)
19 |
20 | @RequiresApi(Build.VERSION_CODES.P)
21 | fun setProfile(image: Bitmap) {
22 | binding.ivProfile.setImageBitmap(image)
23 | binding.ivProfile.invalidate()
24 | }
25 |
26 | fun setName(text: String) {
27 | binding.tvProfileName.text = text
28 | }
29 |
30 | fun setMsg(text: String) {
31 | binding.tvProfileMsg.text = text
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/MapProfileAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.os.Build
6 | import android.view.View
7 | import androidx.annotation.RequiresApi
8 | import com.naver.maps.map.overlay.InfoWindow
9 | import com.rpg.funbox.data.dto.UserDetail
10 | import com.rpg.funbox.data.dto.UserInfoResponse
11 |
12 | class MapProfileAdapter(
13 | private val pContext: Context,
14 | private val userDetail: UserDetail?,
15 | private val image: Bitmap?,
16 | ) : InfoWindow.DefaultViewAdapter(pContext) {
17 | @RequiresApi(Build.VERSION_CODES.P)
18 | override fun getContentView(p0: InfoWindow): View {
19 | val mapProfile = MapProfile(pContext)
20 | if (image != null) {
21 | mapProfile.setProfile(image)
22 | }
23 | mapProfile.invalidate()
24 | userDetail?.let {
25 | mapProfile.setMsg(it.msg)
26 | mapProfile.setName(it.name)
27 | }
28 |
29 | return mapProfile
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/MapUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | sealed class MapUiEvent {
4 |
5 | data class NetworkErrorEvent(val message: String = "Network Error") : MapUiEvent()
6 |
7 | data object LocationPermitted : MapUiEvent()
8 |
9 | data object MessageOpen : MapUiEvent()
10 |
11 | data object ToGame : MapUiEvent()
12 |
13 | data object RejectGame : MapUiEvent()
14 |
15 | data object GetGame : MapUiEvent()
16 |
17 | data object ToSetting : MapUiEvent()
18 |
19 | data object Toggle : MapUiEvent()
20 |
21 | data object GameStart : MapUiEvent()
22 |
23 | data object MessageSubmit : MapUiEvent()
24 |
25 | data object CancelGame : MapUiEvent()
26 |
27 | data object Change : MapUiEvent()
28 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/MessageDialog.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.DialogMessageBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import com.rpg.funbox.presentation.setting.SettingViewModel
11 | import kotlinx.coroutines.flow.collectLatest
12 | import kotlinx.coroutines.launch
13 |
14 | class MessageDialog: BaseDialogFragment(R.layout.dialog_message) {
15 |
16 | private val viewModel: MapViewModel by activityViewModels()
17 |
18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
19 | super.onViewCreated(view, savedInstanceState)
20 | binding.vm = viewModel
21 |
22 | lifecycleScope.launch {
23 | viewModel.mapUiEvent.collectLatest { uiEvent ->
24 | if (uiEvent == MapUiEvent.MessageSubmit) dismiss()
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/OtherUserState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 |
3 | data class OtherUserState (val otherState: OtherState = OtherState.Online){
4 | val canStart : Boolean = (otherState == OtherState.Online)
5 | }
6 |
7 | enum class OtherState {
8 | Online,
9 | Offline,
10 | Playing
11 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/map/SocketApplication.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.map
2 | import com.rpg.funbox.app.MainApplication
3 | import io.socket.client.IO
4 | import io.socket.client.Socket
5 | import java.net.URISyntaxException
6 | import java.util.Collections.singletonList
7 | import java.util.Collections.singletonMap
8 |
9 | class SocketApplication {
10 | companion object {
11 | private lateinit var socket : Socket
12 | fun get(): Socket {
13 | try {
14 | val options = IO.Options()
15 | options.extraHeaders = singletonMap("Authorization",singletonList("Bearer ${MainApplication.mySharedPreferences.getJWT("jwt", "")}"))
16 | socket = IO.socket("http://175.45.193.191:3000/socket",options)
17 | } catch (e: URISyntaxException) {
18 | e.printStackTrace()
19 | }
20 | return socket
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/setting/SetNameDialog.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.setting
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.DialogPositiveBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import kotlinx.coroutines.flow.collectLatest
11 | import kotlinx.coroutines.launch
12 |
13 | class SetNameDialog : BaseDialogFragment(R.layout.dialog_positive) {
14 |
15 | private val viewModel: SettingViewModel by activityViewModels()
16 |
17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
18 | super.onViewCreated(view, savedInstanceState)
19 | binding.vm = viewModel
20 |
21 | lifecycleScope.launch {
22 | viewModel.settingUiEvent.collectLatest {
23 | if (it == SettingUiEvent.CloseSetNameDialog) dismiss()
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/setting/SettingBindingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.setting
2 |
3 | import android.net.Uri
4 | import android.widget.ImageButton
5 | import android.widget.ImageView
6 | import androidx.databinding.BindingAdapter
7 | import coil.load
8 | import com.rpg.funbox.R
9 | import timber.log.Timber
10 | import java.io.IOException
11 |
12 | @BindingAdapter("app:imageUrlBtn")
13 | fun ImageButton.loadImage(uri: Uri?) {
14 | if (uri == null) {
15 | load(R.drawable.profile_none)
16 | } else {
17 | load(uri)
18 | }
19 | }
20 |
21 | @BindingAdapter("app:imageUrl")
22 | fun ImageView.loadImage(uri: Uri?) {
23 | if (uri == null) {
24 | load(R.drawable.profile_none)
25 | } else {
26 | load(uri)
27 | }
28 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/setting/SettingUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.setting
2 |
3 | import com.rpg.funbox.presentation.map.MapUiEvent
4 |
5 | sealed class SettingUiEvent {
6 |
7 | data object GoToMapFragment : SettingUiEvent()
8 |
9 | data object SetName : SettingUiEvent()
10 |
11 | data object SelectProfile : SettingUiEvent()
12 |
13 | data object SetProfile : SettingUiEvent()
14 |
15 | data object StartWithdrawal : SettingUiEvent()
16 |
17 | data object Withdraw : SettingUiEvent()
18 |
19 | data object CloseSetNameDialog : SettingUiEvent()
20 |
21 | data object CloseSetProfileDialog : SettingUiEvent()
22 |
23 | data object ToGame : SettingUiEvent()
24 |
25 | data object RejectGame : SettingUiEvent()
26 |
27 | data object GetGame : SettingUiEvent()
28 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/setting/SettingUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.setting
2 |
3 | import com.rpg.funbox.presentation.login.nickname.NicknameValidState
4 |
5 | data class SettingUiState(
6 | val nicknameValidState: NicknameValidState = NicknameValidState.None
7 | ) {
8 | val isSubmitBtnEnable: Boolean = (nicknameValidState == NicknameValidState.Valid)
9 | }
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/java/com/rpg/funbox/presentation/setting/WithdrawalDialog.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox.presentation.setting
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.activityViewModels
6 | import androidx.lifecycle.lifecycleScope
7 | import com.rpg.funbox.R
8 | import com.rpg.funbox.databinding.DialogRedWithTextBinding
9 | import com.rpg.funbox.presentation.BaseDialogFragment
10 | import kotlinx.coroutines.flow.collectLatest
11 | import kotlinx.coroutines.launch
12 |
13 | class WithdrawalDialog : BaseDialogFragment(R.layout.dialog_red_with_text) {
14 |
15 | private val viewModel: SettingViewModel by activityViewModels()
16 |
17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
18 | super.onViewCreated(view, savedInstanceState)
19 | binding.vm = viewModel
20 |
21 | lifecycleScope.launch {
22 | viewModel.settingUiEvent.collectLatest { uiEvent ->
23 | if (uiEvent == SettingUiEvent.Withdraw) {
24 | dismiss()
25 | }
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/anim/slide_left_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/anim/slide_left_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/add_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/answer_edittext.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/autorenew_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/baseline_arrow_back_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/bg_dialog_rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/bg_green_rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/bg_input_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/bg_red_rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/close_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/fail_button.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/funbox_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/funbox_logo.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/group_1940.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
17 |
20 |
23 |
24 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/map_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/menu_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/my_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/my_location.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/naver_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/naver_login.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/navi_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/navi_icon.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/navigate_next_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/nickname_edittext.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/other_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/other_location.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/profile_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/profile_add.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/profile_game.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/profile_none.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/drawable/profile_none.png
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/round_radius.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/rounded_corner_rect_shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/settings_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/splash_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/success_button.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/wait_background.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/drawable/write_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/activity_game.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
18 |
19 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
19 |
20 |
31 |
32 |
33 |
34 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/activity_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
18 |
19 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
14 |
15 |
29 |
30 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/dialog_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
17 |
18 |
27 |
28 |
39 |
40 |
54 |
55 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/dialog_positive.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
17 |
18 |
27 |
28 |
44 |
45 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/dialog_profile_change.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
18 |
19 |
28 |
29 |
44 |
45 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/dialog_red.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
24 |
25 |
40 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/dialog_red_with_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
18 |
19 |
33 |
34 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/fragment_game_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
18 |
23 |
24 |
34 |
35 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/fragment_network_alert.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
19 |
20 |
33 |
34 |
48 |
49 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/fragment_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
18 |
19 |
31 |
32 |
46 |
47 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/fragment_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
22 |
23 |
34 |
35 |
48 |
49 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/item_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/item_chat_other.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/item_game_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
33 |
34 |
44 |
45 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/map_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
18 |
19 |
32 |
33 |
46 |
47 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/menu_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
18 |
19 |
30 |
31 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/quiz_question_count_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/layout/to_be_determined_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
23 |
24 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/menu/side_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-anydpi-v26/ic_funbox.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-anydpi-v26/ic_funbox_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-anydpi/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_funbox.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_funbox.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_funbox_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_funbox_foreground.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_funbox_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_funbox_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_funbox.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_funbox.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_funbox_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_funbox_foreground.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_funbox_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_funbox_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_funbox.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_funbox.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_funbox_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_funbox_foreground.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_funbox_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_funbox_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_funbox.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_funbox.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_funbox_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_funbox_foreground.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_funbox_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_funbox_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_funbox.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_funbox.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_funbox_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_funbox_foreground.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_funbox_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_funbox_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/navigation/game_nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
21 |
22 |
27 |
28 |
31 |
34 |
37 |
40 |
41 |
42 |
43 |
47 |
48 |
58 |
59 |
63 |
64 |
68 |
69 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/navigation/title_nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
18 |
19 |
20 |
25 |
30 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #FFF5EFE7
6 | #FF00DFA2
7 | #FFE8DFCA
8 | #FFD9D9D9
9 | #00000000
10 | #CCADFF
11 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/values/ic_funbox_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/Android/FunBox/app/src/test/java/com/rpg/funbox/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.rpg.funbox
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/Android/FunBox/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | dependencies {
4 | classpath("com.google.gms:google-services:4.3.10")
5 | }
6 | }
7 |
8 | plugins {
9 | id("com.android.application") version "8.1.3" apply false
10 | id("org.jetbrains.kotlin.android") version "1.9.0" apply false
11 | id("androidx.navigation.safeargs.kotlin") version "2.5.3" apply false
12 | id("com.google.gms.google-services") version "4.4.0" apply false
13 | id("com.google.firebase.appdistribution") version "4.0.0" apply false
14 | }
--------------------------------------------------------------------------------
/Android/FunBox/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.enableJetifier=true
--------------------------------------------------------------------------------
/Android/FunBox/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/Android/FunBox/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Android/FunBox/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 13 10:46:50 KST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/Android/FunBox/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/Android/FunBox/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven("https://naver.jfrog.io/artifactory/maven/")
14 | }
15 | }
16 |
17 | rootProject.name = "FunBox"
18 | include(":app")
19 |
--------------------------------------------------------------------------------
/Android/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/BackEnd/README.md:
--------------------------------------------------------------------------------
1 | # 20231114 Tue
2 | ## 구현한 기능 명세
3 | ### nest.js 서버 설치
4 |
5 | [feat] nest.js 서버 인스톨
6 |
7 | nest.js 서버를 인스톨 했다.
8 | nest.js 프레임워크를 선택한 이유는 아래와 같다.
9 | - 기본 구조가 통일화되어 있어 구조 파악이 명확하고 간편함.
10 | - 자유도가 다소 떨어지지만, 빠른 개발이 가능함
11 | - 타입스크립트를 적극 지원함으로써, 이슈 발생 미연에 방지
12 | - 개발에 자주 사용되는 기능이 내장되어 있어 편리.
13 | ```
14 | sudo npm i -g @nestjs/cli
15 | nest new funbox
16 | ```
17 |
18 | ### 서버 구동 확인
19 | 아래와 같이 잘 작동함을 확인했다.
20 | ```
21 | $ npm run start
22 |
23 | > funbox@0.0.1 start
24 | > nest start
25 |
26 | [Nest] 77419 - 11/14/2023, 3:10:52 PM LOG [NestFactory] Starting Nest application...
27 | [Nest] 77419 - 11/14/2023, 3:10:53 PM LOG [InstanceLoader] AppModule dependencies initialized +70ms
28 | [Nest] 77419 - 11/14/2023, 3:10:53 PM LOG [RoutesResolver] AppController {/}: +32ms
29 | [Nest] 77419 - 11/14/2023, 3:10:53 PM LOG [RouterExplorer] Mapped {/, GET} route +5ms
30 | [Nest] 77419 - 11/14/2023, 3:10:53 PM LOG [NestApplication] Nest application successfully started +6ms
31 | ```
32 |
33 | ### users 모듈 생성
34 | ```
35 | $ nest g module users
36 | ```
37 | ### users 컨트롤러 생성
38 | ```
39 | $ nest g controller users --no-spec
40 | ```
41 | ### users 서비스 생성
42 | ```
43 | $ nest g service users --no-spec
44 | ```
45 | ### users 테스트 메소드 구현
46 | get 요청 응답 확인
47 |
48 | ### [feat]users/location post 응답 초기 구조 구현
49 |
50 | post 메소드로 들어오는 {locX,locY}에 대한 응답을 구현함
51 | 테스트 코드의 fetch에 대해 user[0]의 location이 update 되고,
52 | 주변 유저 1, 2의 정보를 받아오는 것을 확인함.
53 | ```
54 | $ node test.js
55 | [
56 | {
57 | id: 0,
58 | username: 'user0',
59 | created_at: '2023-10-10',
60 | profile_url: 'https://google.com',
61 | locX: 666.6666,
62 | locY: 666.6666,
63 | message: 'hi0',
64 | messaged_at: '2023-10-10'
65 | },
66 | {
67 | id: 1,
68 | username: 'user1',
69 | created_at: '2023-10-10',
70 | profile_url: 'https://google.com',
71 | locX: 123.4568,
72 | locY: 123.4568,
73 | message: 'hi1',
74 | messaged_at: '2023-11-11'
75 | },
76 | {
77 | id: 2,
78 | username: 'user2',
79 | created_at: '2023-10-10',
80 | profile_url: 'https://google.com',
81 | locX: 123.4569,
82 | locY: 123.4569,
83 | message: 'hi2',
84 | messaged_at: '2023-10-10'
85 | }
86 | ]
87 | ```
88 | 그 다음 작업으로
89 | - 유저 정보 DB 구현
90 | - 근처 유저 판별 알고리즘 구현
91 | 이 필요함.
92 |
93 | ## 고민과 해결 과정
94 |
95 | ## 학습 메모
96 |
97 | ## 참고 자료
--------------------------------------------------------------------------------
/BackEnd/funbox/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir: __dirname,
6 | sourceType: 'module',
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/BackEnd/funbox/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | pnpm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # OS
15 | .DS_Store
16 |
17 | # Tests
18 | /coverage
19 | /.nyc_output
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | !.vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/BackEnd/funbox/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | cd BackEnd/funbox && echo 'Server Check' && npx lint-staged && cd ../..
5 |
--------------------------------------------------------------------------------
/BackEnd/funbox/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/BackEnd/funbox/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src",
5 | "compilerOptions": {
6 | "deleteOutDir": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/BackEnd/funbox/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "funbox",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "build": "nest build",
10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"test/**/*.js\"",
11 | "start": "nest start",
12 | "start:dev": "nest start --watch",
13 | "start:debug": "nest start --debug --watch",
14 | "start:prod": "node dist/main",
15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
16 | "test": "jest",
17 | "test:watch": "jest --watch",
18 | "test:cov": "jest --coverage",
19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20 | "test:e2e": "jest --config ./test/jest-e2e.json",
21 | "prepare": "cd ../.. && husky install Backend/funbox/.husky"
22 | },
23 | "dependencies": {
24 | "@nestjs/common": "^10.0.0",
25 | "@nestjs/core": "^10.0.0",
26 | "@nestjs/jwt": "^10.2.0",
27 | "@nestjs/passport": "^10.0.2",
28 | "@nestjs/platform-express": "^10.0.0",
29 | "@nestjs/platform-socket.io": "^10.2.10",
30 | "@nestjs/swagger": "^7.1.16",
31 | "@nestjs/typeorm": "^10.0.1",
32 | "@nestjs/websockets": "^10.2.10",
33 | "@types/passport-jwt": "^3.0.13",
34 | "aws-sdk": "^2.348.0",
35 | "class-transformer": "^0.5.1",
36 | "class-validator": "^0.14.0",
37 | "mysql2": "^3.6.3",
38 | "passport": "^0.6.0",
39 | "passport-jwt": "^4.0.1",
40 | "reflect-metadata": "^0.1.13",
41 | "rxjs": "^7.8.1",
42 | "typeorm": "^0.3.17"
43 | },
44 | "devDependencies": {
45 | "@nestjs/cli": "^10.0.0",
46 | "@nestjs/schematics": "^10.0.0",
47 | "@nestjs/testing": "^10.0.0",
48 | "@types/express": "^4.17.17",
49 | "@types/jest": "^29.5.2",
50 | "@types/multer": "^1.4.11",
51 | "@types/node": "^20.3.1",
52 | "@types/supertest": "^2.0.12",
53 | "@typescript-eslint/eslint-plugin": "^6.0.0",
54 | "@typescript-eslint/parser": "^6.0.0",
55 | "eslint": "^8.42.0",
56 | "eslint-config-prettier": "^9.0.0",
57 | "eslint-plugin-prettier": "^5.0.0",
58 | "husky": "^8.0.3",
59 | "jest": "^29.5.0",
60 | "lint-staged": "^15.1.0",
61 | "prettier": "^3.0.0",
62 | "source-map-support": "^0.5.21",
63 | "supertest": "^6.3.3",
64 | "ts-jest": "^29.1.0",
65 | "ts-loader": "^9.4.3",
66 | "ts-node": "^10.9.1",
67 | "tsconfig-paths": "^4.2.0",
68 | "typescript": "^5.1.3"
69 | },
70 | "jest": {
71 | "moduleFileExtensions": [
72 | "js",
73 | "json",
74 | "ts"
75 | ],
76 | "rootDir": "src",
77 | "testRegex": ".*\\.spec\\.ts$",
78 | "transform": {
79 | "^.+\\.(t|j)s$": "ts-jest"
80 | },
81 | "collectCoverageFrom": [
82 | "**/*.(t|j)s"
83 | ],
84 | "coverageDirectory": "../coverage",
85 | "testEnvironment": "node"
86 | },
87 | "lint-staged": {
88 | "{src,apps,libs,test}/**/*.ts": [
89 | "eslint --fix",
90 | "prettier --write"
91 | ]
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersModule } from './users/users.module';
3 | import { TypeOrmModule } from '@nestjs/typeorm';
4 | import { typeORMConfig } from './configs/typeorm.config';
5 | import { AuthModule } from './auth/auth.module';
6 | import { SocketModule } from './socket/socket.module';
7 |
8 | @Module({
9 | imports: [
10 | TypeOrmModule.forRoot(typeORMConfig),
11 | UsersModule,
12 | AuthModule,
13 | SocketModule,
14 | ],
15 | })
16 | export class AppModule {}
17 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Post } from '@nestjs/common';
2 | import { AuthService } from './auth.service';
3 | import { ApiBody, ApiOkResponse, ApiTags } from '@nestjs/swagger';
4 | import { AuthRequstDto } from './dto/auth-request.dto';
5 | import { AuthResponseDto } from './dto/auth-response.dto';
6 |
7 | @Controller('auth')
8 | @ApiTags('1. 인증 API')
9 | export class AuthController {
10 | constructor(private authService: AuthService) {}
11 |
12 | @Post('/navertoken')
13 | @ApiBody({ type: AuthRequstDto })
14 | @ApiOkResponse({ type: AuthResponseDto })
15 | async getNaverUserAndFindUserDbIfNullMakeNullUser(
16 | @Body() naverAccessDto: AuthRequstDto,
17 | ): Promise {
18 | const naverUserId = await this.authService.getNaverUserId(
19 | naverAccessDto.naverAccessToken,
20 | );
21 |
22 | try {
23 | return await this.authService.findIdOauth(naverUserId);
24 | } catch {
25 | return await this.authService.createNullUser(naverUserId);
26 | }
27 | }
28 |
29 | @Post('/notoken/test')
30 | @ApiBody({
31 | schema: {
32 | type: '{naverFakeId: string}',
33 | example: '{"naverFakeId": "아무string"}',
34 | },
35 | })
36 | @ApiOkResponse({ type: AuthResponseDto })
37 | async getFakeNaverUserAndFindUserDbIfNotMakeNullUser(
38 | @Body('naverFakeId') naverFakeId: string,
39 | ) {
40 | const naverUserId = naverFakeId;
41 |
42 | try {
43 | return await this.authService.findIdOauth(naverUserId);
44 | } catch {
45 | return await this.authService.createNullUser(naverUserId);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { AuthController } from './auth.controller';
3 | import { AuthService } from './auth.service';
4 | import { JwtModule } from '@nestjs/jwt';
5 | import { PassportModule } from '@nestjs/passport';
6 | import { JwtStrategy } from './jwt.strategy';
7 |
8 | @Module({
9 | imports: [
10 | PassportModule.register({ defaultStrategy: 'jwt' }),
11 | JwtModule.register({
12 | secret: process.env.JWT_SECRET,
13 | signOptions: {
14 | expiresIn: '3d',
15 | },
16 | }),
17 | ],
18 | controllers: [AuthController],
19 | providers: [AuthService, JwtStrategy],
20 | exports: [AuthService, JwtStrategy, PassportModule],
21 | })
22 | export class AuthModule {}
23 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ConflictException,
3 | Injectable,
4 | InternalServerErrorException,
5 | NotFoundException,
6 | UnauthorizedException,
7 | } from '@nestjs/common';
8 | import { User } from 'src/users/user.entity';
9 |
10 | import * as crypto from 'crypto';
11 | import { JwtService } from '@nestjs/jwt';
12 | import { AuthResponseDto } from './dto/auth-response.dto';
13 | import { UserAuthDto } from './dto/user-auth.dto';
14 |
15 | @Injectable()
16 | export class AuthService {
17 | constructor(private jwtService: JwtService) {}
18 |
19 | async tokenValidation(accessToken: string): Promise {
20 | try {
21 | const decode = await this.jwtService.verifyAsync(accessToken, {
22 | secret: process.env.JWT_SECRET,
23 | });
24 | const { id } = decode;
25 | return { id };
26 | } catch (error) {
27 | throw new UnauthorizedException('Invalid access token');
28 | }
29 | }
30 |
31 | async getNaverUserId(accessToken: string): Promise {
32 | const url = 'https://openapi.naver.com/v1/nid/me';
33 | const response = await fetch(url, {
34 | method: 'GET',
35 | headers: {
36 | Authorization: 'Bearer ' + accessToken,
37 | },
38 | });
39 | const userInfo = await response.json();
40 | if (userInfo.message !== 'success') {
41 | throw new UnauthorizedException('invalid NAVER access token');
42 | }
43 | const userId = userInfo.response.id;
44 | return userId;
45 | }
46 |
47 | async createNullUser(idOauth: string): Promise {
48 | const user = new User();
49 | this.setUserNull(user, idOauth);
50 |
51 | try {
52 | await user.save();
53 | } catch (error) {
54 | if (error.code === 'ER_DUP_ENTRY') {
55 | throw new ConflictException('Existing id (OAuth)');
56 | } else {
57 | console.log(error);
58 | throw new InternalServerErrorException();
59 | }
60 | }
61 |
62 | const jwtString = this.jwtService.sign(UserAuthDto.of(user));
63 | return AuthResponseDto.of(jwtString);
64 | }
65 |
66 | setUserNull(user: User, idOauth: string): User {
67 | user.username = null;
68 | user.created_at = new Date();
69 | user.profile_url = null;
70 | user.locX = null;
71 | user.locY = null;
72 | user.message = null;
73 | user.messaged_at = null;
74 | user.type_login = 'NAVER';
75 | user.id_oauth = this.hashedId(idOauth);
76 |
77 | return user;
78 | }
79 |
80 | hashedId(idOauth: string): string {
81 | const salt = process.env.SALT_OAUTHID;
82 | return crypto
83 | .createHash('sha256')
84 | .update(idOauth + salt)
85 | .digest('hex');
86 | }
87 |
88 | async findIdOauth(idOauth: string): Promise {
89 | const user = await User.findOne({
90 | where: { id_oauth: this.hashedId(idOauth) },
91 | });
92 |
93 | if (!user) {
94 | throw new NotFoundException(`Can't find User with idOauth`);
95 | }
96 |
97 | const jwtString = this.jwtService.sign(UserAuthDto.of(user));
98 | return AuthResponseDto.of(jwtString);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/dto/auth-request.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class AuthRequstDto {
4 | @ApiProperty({
5 | description: 'NAVER AccessToken',
6 | example: 'NAVER Access Token',
7 | })
8 | naverAccessToken: string;
9 | }
10 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/dto/auth-response.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class AuthResponseDto {
4 | @ApiProperty({
5 | description: 'jwt-accesstoken',
6 | example: 'jwt 형식의 string',
7 | })
8 | accessToken: string;
9 |
10 | static of(jwtString: string): AuthResponseDto {
11 | return { accessToken: jwtString };
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/dto/user-auth.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 | import { User } from 'src/users/user.entity';
3 |
4 | export class UserAuthDto {
5 | @ApiProperty({ description: 'id' })
6 | id: number;
7 |
8 | static of(user: User): UserAuthDto {
9 | return { id: user.id };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/auth/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { ExtractJwt, Strategy } from 'passport-jwt';
4 | import { User } from 'src/users/user.entity';
5 | import { UserAuthDto } from './dto/user-auth.dto';
6 |
7 | @Injectable()
8 | export class JwtStrategy extends PassportStrategy(Strategy) {
9 | constructor() {
10 | super({
11 | secretOrKey: process.env.JWT_SECRET,
12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
13 | });
14 | }
15 |
16 | async validate(payload) {
17 | const { id } = payload;
18 | const user = await User.findOne({ where: { id: id } });
19 | if (!user) {
20 | throw new UnauthorizedException('there is not that user');
21 | }
22 | return UserAuthDto.of(user);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/configs/swagger.config.ts:
--------------------------------------------------------------------------------
1 | import { DocumentBuilder } from '@nestjs/swagger';
2 |
3 | export const swaggerConfig = new DocumentBuilder()
4 | .setTitle('FunBox API Document')
5 | .setDescription('FunBox 프로젝트의 API 문서입니다.')
6 | .setVersion('1.0.0')
7 | .addBearerAuth()
8 | .build();
9 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/configs/typeorm.config.ts:
--------------------------------------------------------------------------------
1 | import { TypeOrmModuleOptions } from '@nestjs/typeorm';
2 |
3 | export const typeORMConfig: TypeOrmModuleOptions = {
4 | type: 'mysql',
5 | host: process.env.DB_ADDRESS,
6 | port: 3306,
7 | username: process.env.DB_USERNAME,
8 | password: process.env.DB_PASSWORD,
9 | database: 'funbox',
10 | entities: [__dirname + '/../**/*.entity.{js,ts}'],
11 | synchronize: true,
12 | };
13 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { SwaggerModule } from '@nestjs/swagger';
3 | import { AppModule } from './app.module';
4 | import { swaggerConfig } from './configs/swagger.config';
5 |
6 | async function bootstrap() {
7 | const app = await NestFactory.create(AppModule);
8 |
9 | const document = SwaggerModule.createDocument(app, swaggerConfig);
10 | SwaggerModule.setup('api', app, document);
11 |
12 | await app.listen(3000);
13 | }
14 | bootstrap();
15 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/socket/dto/game-room.dto.ts:
--------------------------------------------------------------------------------
1 | import { Socket } from 'socket.io';
2 |
3 | export class GameRoomDto {
4 | id: string;
5 | players: Socket[];
6 | quizzes: string[];
7 | round: number;
8 | }
9 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/socket/enum/game-apply-answer.enum.ts:
--------------------------------------------------------------------------------
1 | export enum GameApplyAnswer {
2 | OFFLINE = 'OFFLINE',
3 | PLAYING = 'PLAYING',
4 | ACCEPT = 'ACCEPT',
5 | REJECT = 'REJECT',
6 | }
7 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/socket/filters/ws-exception.filter.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentsHost, Catch, HttpException } from '@nestjs/common';
2 | import { WsException } from '@nestjs/websockets';
3 | import { Socket } from 'socket.io';
4 |
5 | @Catch(WsException, HttpException)
6 | export class WsExceptionFilter {
7 | public catch(exception: HttpException | WsException, host: ArgumentsHost) {
8 | const client = host.switchToWs().getClient();
9 | this.handleError(client, exception);
10 | }
11 |
12 | public handleError(client: Socket, exception: HttpException | WsException) {
13 | if (exception instanceof HttpException) {
14 | client.emit('error', JSON.stringify(exception.getResponse()));
15 | } else {
16 | client.emit('error', JSON.stringify(exception.getError()));
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/socket/pipes/answer-validation.pipe.ts:
--------------------------------------------------------------------------------
1 | import { BadRequestException, PipeTransform } from '@nestjs/common';
2 | import { GameApplyAnswer } from '../enum/game-apply-answer.enum';
3 |
4 | export class AnswerValidationPipe implements PipeTransform {
5 | readonly answerOptions = [GameApplyAnswer.ACCEPT, GameApplyAnswer.REJECT];
6 |
7 | transform(value: any) {
8 | if (!value) {
9 | throw new BadRequestException(`answer is empty`);
10 | }
11 | value = value.toUpperCase();
12 | if (!this.isAnswerValid(value)) {
13 | throw new BadRequestException(`${value} isn't (ACCEPT or REJECT)`);
14 | }
15 | return value;
16 | }
17 |
18 | private isAnswerValid(answer: any) {
19 | const index = this.answerOptions.indexOf(answer);
20 | return index !== -1;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/socket/socket.gateway.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ConnectedSocket,
3 | MessageBody,
4 | OnGatewayConnection,
5 | SubscribeMessage,
6 | WebSocketGateway,
7 | WebSocketServer,
8 | } from '@nestjs/websockets';
9 | import { Server, Socket } from 'socket.io';
10 | import { SocketService } from './socket.service';
11 | import {
12 | Logger,
13 | ParseBoolPipe,
14 | ParseIntPipe,
15 | UseFilters,
16 | } from '@nestjs/common';
17 | import { WsExceptionFilter } from './filters/ws-exception.filter';
18 | import { GameApplyAnswer } from './enum/game-apply-answer.enum';
19 | import { AnswerValidationPipe } from './pipes/answer-validation.pipe';
20 |
21 | @WebSocketGateway({
22 | namespace: 'socket',
23 | cors: { origin: '*' },
24 | })
25 | @UseFilters(WsExceptionFilter)
26 | export class SocketGateway implements OnGatewayConnection {
27 | @WebSocketServer()
28 | private server: Server;
29 | private logger: Logger = new Logger('SocketGateway');
30 |
31 | constructor(private socketService: SocketService) {}
32 |
33 | handleConnection(client: Socket): void {
34 | this.logger.log(`handleConnection: ${client.id}`);
35 | this.socketService.handleConnection(client);
36 | }
37 |
38 | @SubscribeMessage('gameApply')
39 | gameApply(
40 | @ConnectedSocket() client: Socket,
41 | @MessageBody('opponentId', ParseIntPipe) opponentId: number,
42 | ): void {
43 | const log = JSON.stringify({ opponentId });
44 | this.logger.log(`gameApply from ${client.data.userId}: ${log}`);
45 | this.socketService.gameApply(client, opponentId);
46 | }
47 |
48 | @SubscribeMessage('gameApplyAnswer')
49 | gameApplyAnswer(
50 | @ConnectedSocket() client: Socket,
51 | @MessageBody('answer', AnswerValidationPipe) answer: GameApplyAnswer,
52 | ): void {
53 | const log = JSON.stringify({ answer });
54 | this.logger.log(`gameApplyAnswer from ${client.data.userId}: ${log}`);
55 | this.socketService.gameApplyAnswer(client, answer);
56 | }
57 |
58 | @SubscribeMessage('quizAnswer')
59 | quizAnswer(
60 | @ConnectedSocket() client: Socket,
61 | @MessageBody('answer') answer: string,
62 | ): void {
63 | const log = JSON.stringify({ answer });
64 | this.logger.log(`quizAnswer from ${client.data.userId}: ${log}`);
65 | this.socketService.quizAnswer(client, answer);
66 | }
67 |
68 | @SubscribeMessage('verifyAnswer')
69 | verifyAnswer(
70 | @ConnectedSocket() client: Socket,
71 | @MessageBody('isCorrect', ParseBoolPipe) isCorrect: boolean,
72 | ): void {
73 | const log = JSON.stringify({ isCorrect });
74 | this.logger.log(`verifyAnswer from ${client.data.userId}: ${log}`);
75 | this.socketService.verifyAnswer(client, isCorrect);
76 | }
77 |
78 | @SubscribeMessage('quitGame')
79 | quitGame(@ConnectedSocket() client: Socket): void {
80 | this.logger.log(`quitGame from ${client.data.userId}`);
81 | this.socketService.quitGame(client);
82 | }
83 |
84 | @SubscribeMessage('directMessage')
85 | directMessage(
86 | @ConnectedSocket() client: Socket,
87 | @MessageBody('userId', ParseIntPipe) userId: number,
88 | @MessageBody('message') message: string,
89 | ): void {
90 | const log = JSON.stringify({ userId, message });
91 | this.logger.log(`directMessage from ${client.data.userId}: ${log}`);
92 | this.socketService.directMessage(client, userId, message);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/socket/socket.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { SocketGateway } from './socket.gateway';
3 | import { SocketService } from './socket.service';
4 | import { AuthModule } from 'src/auth/auth.module';
5 | import { UsersModule } from 'src/users/users.module';
6 | import { GameService } from './game.service';
7 |
8 | @Module({
9 | imports: [AuthModule, UsersModule],
10 | providers: [SocketGateway, SocketService, GameService],
11 | })
12 | export class SocketModule {}
13 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/users/dto/near-users.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 | import { User } from '../user.entity';
3 |
4 | export class NearUsersDto {
5 | @ApiProperty({ description: 'id' })
6 | id: number;
7 |
8 | @ApiProperty({ description: '이름' })
9 | username: string;
10 |
11 | @ApiProperty({ description: '유저 위치 X좌표' })
12 | locX: number;
13 |
14 | @ApiProperty({ description: '유저 위치 Y좌표' })
15 | locY: number;
16 |
17 | @ApiProperty({ description: '1시간 내에 메시지가 변경된 경우 Ture' })
18 | isMsgInAnHour: boolean;
19 |
20 | static of(user: User): NearUsersDto {
21 | const { id, username, locX, locY } = user;
22 | let isMsgInAnHour: boolean;
23 | if (user.messaged_at === null) {
24 | isMsgInAnHour = false;
25 | } else {
26 | isMsgInAnHour = user.messaged_at.getTime() > Date.now() - 3600000;
27 | }
28 | return { id, username, locX, locY, isMsgInAnHour };
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/users/dto/user-location.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class UserLocationDto {
4 | @ApiProperty({ description: '유저 위치 X좌표' })
5 | locX: number;
6 |
7 | @ApiProperty({ description: '유저 위치 Y좌표' })
8 | locY: number;
9 | }
10 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/users/dto/user-request.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class UserRequestDto {
4 | @ApiProperty({
5 | description: '이름',
6 | example: '15자 이내의 string',
7 | })
8 | username: string;
9 |
10 | @ApiProperty({
11 | example: 'askjasl123123kfja1232lsdkfj.png',
12 | description: '프로필 사진 URL',
13 | })
14 | profileUrl: string;
15 |
16 | @ApiProperty({
17 | description: '유저 메시지',
18 | example: '31자 이내의 string',
19 | })
20 | message: string;
21 | }
22 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/users/dto/user-response.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 | import { User } from '../user.entity';
3 |
4 | export class UserResponseDto {
5 | @ApiProperty({
6 | description: '이름',
7 | example: '15자 이내의 string',
8 | })
9 | username: string;
10 |
11 | @ApiProperty({
12 | example: 'askjasl123123kfja1232lsdkfj.png',
13 | description: '프로필 사진 URL',
14 | })
15 | profileUrl: string;
16 |
17 | @ApiProperty({
18 | description: '유저 메시지',
19 | example: '31자 이내의 string',
20 | })
21 | message: string;
22 |
23 | static of(user: User): UserResponseDto {
24 | return {
25 | username: user.username,
26 | profileUrl: user.profile_url,
27 | message: user.message,
28 | };
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/users/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 | import {
3 | BaseEntity,
4 | Column,
5 | Entity,
6 | PrimaryGeneratedColumn,
7 | Unique,
8 | } from 'typeorm';
9 |
10 | @Entity()
11 | @Unique(['id_oauth'])
12 | export class User extends BaseEntity {
13 | @PrimaryGeneratedColumn()
14 | @ApiProperty({
15 | description: 'id',
16 | })
17 | id: number;
18 |
19 | @Column({ length: 15, nullable: true })
20 | @ApiProperty({
21 | description: '이름',
22 | })
23 | username: string;
24 |
25 | @Column({ type: 'timestamp', nullable: false })
26 | @ApiProperty({ description: '유저 생성 시간' })
27 | created_at: Date;
28 |
29 | @Column({ length: 255, nullable: true })
30 | @ApiProperty({
31 | example: 'sldkfjglkcvjboweirjglskdfjsdfkgjlsdfkg.png',
32 | description: '프로필 사진 URL',
33 | })
34 | profile_url: string;
35 |
36 | @Column({ type: 'double', nullable: true })
37 | @ApiProperty({ description: '유저 위치 X좌표' })
38 | locX: number;
39 |
40 | @Column({ type: 'double', nullable: true })
41 | @ApiProperty({ description: '유저 위치 Y좌표' })
42 | locY: number;
43 |
44 | @Column({ length: 31, nullable: true })
45 | @ApiProperty({ description: '유저 메시지' })
46 | message: string;
47 |
48 | @Column({ type: 'timestamp', nullable: true })
49 | @ApiProperty({ description: '메시지가 작성된 시간' })
50 | messaged_at: Date;
51 |
52 | @Column({ length: 31, nullable: false })
53 | type_login: string;
54 |
55 | @Column({ length: 127, nullable: false })
56 | id_oauth: string;
57 |
58 | @Column({ type: 'timestamp', nullable: true })
59 | last_polling_at: Date;
60 | }
61 |
--------------------------------------------------------------------------------
/BackEnd/funbox/src/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersController } from './users.controller';
3 | import { UsersService } from './users.service';
4 | import { AuthModule } from 'src/auth/auth.module';
5 |
6 | @Module({
7 | imports: [AuthModule],
8 | controllers: [UsersController],
9 | providers: [UsersService],
10 | exports: [UsersService],
11 | })
12 | export class UsersModule {}
13 |
--------------------------------------------------------------------------------
/BackEnd/funbox/test/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm2023/and05-funbox/54835eff4fd565bc48690de51d31b0cf4e07b94f/BackEnd/funbox/test/test.png
--------------------------------------------------------------------------------
/BackEnd/funbox/test/test_apis.js:
--------------------------------------------------------------------------------
1 | const accessToken =
2 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzgsInVzZXJuYW1lIjpudWxsLCJpYXQiOjE3MDEwODAyOTAsImV4cCI6MTcwMTMzOTQ5MH0.W4iJQtlHwjqR5YOgOlLyQxCd-JtKsFrEVh8ptUOP1Fs';
3 |
4 | fetch('http://localhost:3000/auth/notoken/test', {
5 | method: 'POST',
6 | headers: {
7 | 'Content-Type': 'application/json',
8 | },
9 | body: JSON.stringify({ naverFakeId: 'test1' }),
10 | })
11 | .then((res) => {
12 | return res.json();
13 | })
14 | .then((data) => {
15 | console.log('------------------');
16 | console.log('테스트 : 네이버 Fake id로 user DB 생성');
17 | console.log(data);
18 | });
19 |
20 | fetch('http://localhost:3000/users/username', {
21 | method: 'POST',
22 | headers: {
23 | 'Content-Type': 'application/json',
24 | Authorization: `Bearer ${accessToken}`,
25 | },
26 | body: JSON.stringify({
27 | userName: 'hi',
28 | }),
29 | })
30 | .then((res) => {
31 | return res.json();
32 | })
33 | .then((data) => {
34 | console.log('------------------');
35 | console.log('username 변경 확인');
36 | console.log(data);
37 | });
38 |
--------------------------------------------------------------------------------
/BackEnd/funbox/test/test_date.js:
--------------------------------------------------------------------------------
1 | const now = new Date();
2 | console.log(now);
3 | const b = new Date();
4 | b.setSeconds(now.getSeconds() - 10);
5 | console.log(b);
6 |
--------------------------------------------------------------------------------
/BackEnd/funbox/test/test_profile.js:
--------------------------------------------------------------------------------
1 | const accessToken =
2 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEsImlhdCI6MTcwMTU4NTgyOSwiZXhwIjoxNzAxODQ1MDI5fQ.pXD_hEQ61MKOVzodUXo9j_3h_UCJmZcGDDAQ__2crI4';
3 | // fetch('http://localhost:3000/users/profile', {
4 | // method: 'POST',
5 | // headers: {
6 | // 'Content-Type': 'application/json',
7 | // },
8 | // body: JSON.stringify({
9 | // accessToken: accessToken,
10 | // profileUrl: 'hello',
11 | // }),
12 | // })
13 | // .then((res) => {
14 | // return res.json();
15 | // })
16 | // .then((data) => {
17 | // console.log('------------------');
18 | // console.log('profile_url 변경 확인');
19 | // console.log(data);
20 | // });
21 |
22 | const fs = require('fs');
23 | const FormData = require('form-data');
24 | const fetch = require('node-fetch');
25 |
26 | const filePath = `./test.png`;
27 | const form = new FormData();
28 | const stats = fs.statSync(filePath);
29 | const fileSizeInBytes = stats.size;
30 | const fileStream = fs.createReadStream(filePath);
31 | form.append('file', fileStream, { knownLength: fileSizeInBytes });
32 |
33 | fetch('http://localhost:3000/users/profile', {
34 | method: 'POST',
35 | headers: {
36 | Authorization: `Bearer ${accessToken}`,
37 | },
38 | credentials: 'include',
39 | body: form,
40 | })
41 | .then((res) => {
42 | return res.json();
43 | })
44 | .then((data) => {
45 | console.log('------------유저 프로필 갱신');
46 | console.log(data);
47 | });
48 |
49 | // fetch('http://localhost:3000/users/profile', {
50 | // method: 'DELETE',
51 | // headers: {
52 | // Authorization: `Bearer ${accessToken}`,
53 | // },
54 | // })
55 | // .then((res) => {
56 | // return res.json();
57 | // })
58 | // .then((data) => {
59 | // console.log('------------유저 프로필 삭제');
60 | // console.log(data);
61 | // });
62 |
--------------------------------------------------------------------------------
/BackEnd/funbox/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/BackEnd/funbox/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "ES2021",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "baseUrl": "./",
13 | "incremental": true,
14 | "skipLibCheck": true,
15 | "strictNullChecks": false,
16 | "noImplicitAny": false,
17 | "strictBindCallApply": false,
18 | "forceConsistentCasingInFileNames": false,
19 | "noFallthroughCasesInSwitch": false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/docs/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # 제목
2 | ### PR 타입
3 | - [ ] 기능 추가
4 | - [ ] 기능 삭제
5 | - [ ] 버그 수정
6 | - [ ] 의존성, 환경변수, 빌드관련 코드 업데이트
7 |
8 | ### 반영 브랜치
9 | ex) feat/login -> dev
10 |
11 | ### 변경사항
12 | ex) 로그인시, 구글 소셜 로그인 기능 추가
13 | 필요시 사진 첨부
14 |
15 | ### 테스트 결과
16 | ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. 결과물에 대한 스크린샷, GIF, 혹은 라이브 데모가 가능하도록 샘플API를 첨부할 수도 있습니다.
17 |
18 |
19 |
--------------------------------------------------------------------------------