├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_template.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── android.yml │ ├── qa_apk.yml │ └── release_tag.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── libs │ └── libDaumMapAndroid.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── eatssu │ │ └── android │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── eatssu │ │ │ └── android │ │ │ ├── App.kt │ │ │ ├── alarm │ │ │ └── NotificationReceiver.kt │ │ │ ├── data │ │ │ ├── MySharedPreferences.kt │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── ChangeNicknameRequest.kt │ │ │ │ │ ├── ChangePwRequest.kt │ │ │ │ │ ├── CheckValidTokenRequest.kt │ │ │ │ │ ├── LoginWithKakaoRequest.kt │ │ │ │ │ ├── ModifyReviewRequest.kt │ │ │ │ │ ├── ReportRequest.kt │ │ │ │ │ └── WriteReviewRequest.kt │ │ │ │ └── response │ │ │ │ │ ├── BaseResponse.kt │ │ │ │ │ ├── ImageResponse.kt │ │ │ │ │ ├── MealResponse.kt │ │ │ │ │ ├── MealReviewInfoResponse.kt │ │ │ │ │ ├── MenuOfMealResponse.kt │ │ │ │ │ ├── MenuResponse.kt │ │ │ │ │ ├── MenuReviewInfoResponse.kt │ │ │ │ │ ├── MyInfoResponse.kt │ │ │ │ │ ├── MyReviewResponse.kt │ │ │ │ │ ├── ReviewListResponse.kt │ │ │ │ │ └── TokenResponse.kt │ │ │ ├── enums │ │ │ │ ├── MenuType.kt │ │ │ │ ├── Provider.kt │ │ │ │ ├── ReportType.kt │ │ │ │ ├── Restaurant.kt │ │ │ │ └── Time.kt │ │ │ ├── repository │ │ │ │ ├── FirebaseRemoteConfigRepository.kt │ │ │ │ ├── MealRepositoryImpl.kt │ │ │ │ ├── OauthRepositoryImpl.kt │ │ │ │ ├── PreferencesRepository.kt │ │ │ │ ├── ReportRepositoryImpl.kt │ │ │ │ ├── ReviewRepositoryImpl.kt │ │ │ │ └── UserRepositoryImpl.kt │ │ │ └── service │ │ │ │ ├── MealService.kt │ │ │ │ ├── MenuService.kt │ │ │ │ ├── OauthService.kt │ │ │ │ ├── ReportService.kt │ │ │ │ ├── ReviewService.kt │ │ │ │ └── UserService.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── DataModule.kt │ │ │ ├── NetworkModule.kt │ │ │ ├── ServiceModule.kt │ │ │ └── network │ │ │ │ ├── TokenAuthenticator.kt │ │ │ │ └── TokenInterceptor.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── AndroidMessage.kt │ │ │ │ ├── CalendarData.kt │ │ │ │ ├── Menu.kt │ │ │ │ ├── MenuMini.kt │ │ │ │ ├── RestaurantInfo.kt │ │ │ │ ├── Review.kt │ │ │ │ ├── ReviewInfo.kt │ │ │ │ ├── Section.kt │ │ │ │ └── TokenState.kt │ │ │ ├── repository │ │ │ │ ├── MealRepository.kt │ │ │ │ ├── OauthRepository.kt │ │ │ │ ├── ReportRepository.kt │ │ │ │ ├── ReviewRepository.kt │ │ │ │ └── UserRepository.kt │ │ │ └── usecase │ │ │ │ ├── alarm │ │ │ │ ├── AlarmUsecase.kt │ │ │ │ ├── GetDailyNotificationStatusUseCase.kt │ │ │ │ └── SetDailyNotificationStatusUseCase.kt │ │ │ │ ├── auth │ │ │ │ ├── GetAccessTokenUseCase.kt │ │ │ │ ├── GetIsAccessTokenValidUseCase.kt │ │ │ │ ├── GetMyReviewsUseCase.kt │ │ │ │ ├── GetRefreshTokenUseCase.kt │ │ │ │ ├── GetUserEmailUseCase.kt │ │ │ │ ├── GetUserInfoUseCase.kt │ │ │ │ ├── GetUserNameUseCase.kt │ │ │ │ ├── LoginUseCase.kt │ │ │ │ ├── LogoutUseCase.kt │ │ │ │ ├── ReissueTokenUseCase.kt │ │ │ │ ├── SetAccessTokenUseCase.kt │ │ │ │ ├── SetRefreshTokenUseCase.kt │ │ │ │ ├── SetUserEmailUseCase.kt │ │ │ │ ├── SetUserNameUseCase.kt │ │ │ │ ├── SignOutUseCase.kt │ │ │ │ └── ValidateUserNameUseCase.kt │ │ │ │ ├── menu │ │ │ │ └── GetMenuNameListOfMealUseCase.kt │ │ │ │ └── review │ │ │ │ ├── DeleteReviewUseCase.kt │ │ │ │ ├── GetImageUrlUseCase.kt │ │ │ │ ├── GetMealReviewInfoUseCase.kt │ │ │ │ ├── GetMealReviewListUseCase.kt │ │ │ │ ├── GetMenuReviewInfoUseCase.kt │ │ │ │ ├── GetMenuReviewListUseCase.kt │ │ │ │ ├── ModifyReviewUseCase.kt │ │ │ │ ├── PostReportUseCase.kt │ │ │ │ └── WriteReviewUseCase.kt │ │ │ └── presentation │ │ │ ├── MainActivity.kt │ │ │ ├── MainViewModel.kt │ │ │ ├── UiEvent.kt │ │ │ ├── UiState.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ └── TokenEventBus.kt │ │ │ ├── cafeteria │ │ │ ├── CafeteriaFragment.kt │ │ │ ├── CafeteriaViewPagerAdapter.kt │ │ │ ├── calendar │ │ │ │ ├── CalendarAdapter.kt │ │ │ │ └── CalendarViewHolder.kt │ │ │ ├── info │ │ │ │ ├── InfoBottomSheetFragment.kt │ │ │ │ └── InfoViewModel.kt │ │ │ ├── menu │ │ │ │ ├── MenuAdapter.kt │ │ │ │ ├── MenuFragment.kt │ │ │ │ ├── MenuSubAdapter.kt │ │ │ │ └── MenuViewModel.kt │ │ │ └── review │ │ │ │ ├── list │ │ │ │ ├── ReviewActivity.kt │ │ │ │ ├── ReviewAdapter.kt │ │ │ │ └── ReviewViewModel.kt │ │ │ │ ├── modify │ │ │ │ ├── ModifyReviewActivity.kt │ │ │ │ └── ModifyViewModel.kt │ │ │ │ ├── report │ │ │ │ ├── ReportActivity.kt │ │ │ │ └── ReportViewModel.kt │ │ │ │ └── write │ │ │ │ ├── ReviewWriteRateActivity.kt │ │ │ │ ├── ReviewWriteViewModel.kt │ │ │ │ └── menu │ │ │ │ ├── ReviewWriteMenuActivity.kt │ │ │ │ ├── VariableMenuPickAdapter.kt │ │ │ │ └── VariableMenuViewModel.kt │ │ │ ├── common │ │ │ ├── AndroidMessageDialogActivity.kt │ │ │ ├── ForceUpdateActivity.kt │ │ │ ├── MyReviewBottomSheetFragment.kt │ │ │ ├── NetworkConnection.kt │ │ │ ├── OthersBottomSheetFragment.kt │ │ │ ├── VersionViewModel.kt │ │ │ └── VersionViewModelFactory.kt │ │ │ ├── compose │ │ │ └── ui │ │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ ├── login │ │ │ ├── IntroActivity.kt │ │ │ ├── IntroViewModel.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── LoginViewModel.kt │ │ │ └── UserApiClient.kt │ │ │ ├── map │ │ │ ├── MapFragment.kt │ │ │ └── MapFragmentComposeView.kt │ │ │ ├── mypage │ │ │ ├── DeveloperActivity.kt │ │ │ ├── MyPageFragment.kt │ │ │ ├── MyPageViewModel.kt │ │ │ ├── SignOutActivity.kt │ │ │ ├── SignOutViewModel.kt │ │ │ ├── myreview │ │ │ │ ├── MyReviewAdapter.kt │ │ │ │ ├── MyReviewListActivity.kt │ │ │ │ └── MyReviewViewModel.kt │ │ │ ├── terms │ │ │ │ └── WebViewActivity.kt │ │ │ └── usernamechange │ │ │ │ ├── UserNameChangeActivity.kt │ │ │ │ └── UserNameChangeViewModel.kt │ │ │ └── util │ │ │ ├── ActivityUtil.kt │ │ │ ├── CalendarUtil.kt │ │ │ └── ContextUtil.kt │ └── res │ │ ├── color │ │ ├── selector_bottom_navi_item.xml │ │ └── selector_calendar_colortext.xml │ │ ├── drawable-v24 │ │ ├── ic_add_pic.png │ │ ├── ic_bell.png │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_next.png │ │ ├── ic_profile.png │ │ ├── ic_review.png │ │ └── ic_setting.png │ │ ├── drawable │ │ ├── ic_alarm_logo.xml │ │ ├── ic_arrow_left.png │ │ ├── ic_arrow_right.png │ │ ├── ic_bad_28.png │ │ ├── ic_baseline_camera_alt_24.xml │ │ ├── ic_cafeteria_menu.xml │ │ ├── ic_check_24.png │ │ ├── ic_delete.png │ │ ├── ic_good_28.png │ │ ├── ic_info_12.png │ │ ├── ic_kakao_login.png │ │ ├── ic_launcher_background.xml │ │ ├── ic_left_arrow.xml │ │ ├── ic_location.xml │ │ ├── ic_map.xml │ │ ├── ic_menu_24.png │ │ ├── ic_mypage.xml │ │ ├── ic_none_review.png │ │ ├── ic_pencil.xml │ │ ├── ic_remove.xml │ │ ├── ic_right_arrow.xml │ │ ├── ic_selector_background_white.xml │ │ ├── ic_three_dot.png │ │ ├── ic_uncheck_24.png │ │ ├── ic_unselected_24.png │ │ ├── ic_unsubscribe_16.png │ │ ├── img_backgroud_snow.png │ │ ├── img_dodam.jpeg │ │ ├── img_kakao_login_btn.png │ │ ├── img_logo_512.png │ │ ├── img_logo_christmas.png │ │ ├── img_logo_snow.png │ │ ├── img_member.png │ │ ├── img_new_logo_primary.png │ │ ├── img_new_logo_white.png │ │ ├── img_splash_christmas.png │ │ ├── img_splash_server_fix_contents.png │ │ ├── layer_bottom_shadow.xml │ │ ├── layer_progress.xml │ │ ├── selector_background_blue.xml │ │ ├── selector_check_state.xml │ │ ├── selector_report.xml │ │ ├── selector_toggle.xml │ │ ├── shape_button_duplicate.xml │ │ ├── shape_cafeteria_section.xml │ │ ├── shape_corner_bottom.xml │ │ ├── shape_corner_top.xml │ │ ├── shape_edittext_small_gray.xml │ │ ├── shape_menu_name.xml │ │ ├── shape_report.xml │ │ ├── shape_report_select.xml │ │ ├── shape_round_corners_dialog.xml │ │ ├── shape_shadow.xml │ │ ├── shape_text_field_small.xml │ │ ├── shape_text_field_small_red.xml │ │ ├── shape_toggle.xml │ │ └── shape_transparent_calendar_element.xml │ │ ├── font-v26 │ │ └── font.xml │ │ ├── font │ │ ├── pretendard_black.ttf │ │ ├── pretendard_bold.ttf │ │ ├── pretendard_extrabold.ttf │ │ ├── pretendard_extralight.ttf │ │ ├── pretendard_light.ttf │ │ ├── pretendard_medium.ttf │ │ ├── pretendard_regular.ttf │ │ ├── pretendard_semibold.ttf │ │ └── pretendard_thin.ttf │ │ ├── layout │ │ ├── activity_base.xml │ │ ├── activity_developer.xml │ │ ├── activity_fix_menu.xml │ │ ├── activity_info.xml │ │ ├── activity_intro.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_my_review_list.xml │ │ ├── activity_others_review_dialog.xml │ │ ├── activity_report.xml │ │ ├── activity_review.xml │ │ ├── activity_review_write_menu.xml │ │ ├── activity_review_write_rate.xml │ │ ├── activity_sign_out.xml │ │ ├── activity_user_name_change.xml │ │ ├── activity_webview.xml │ │ ├── calendar_cell.xml │ │ ├── dialog_force_update.xml │ │ ├── fragment_bottomsheet_info.xml │ │ ├── fragment_bottomsheet_my_review.xml │ │ ├── fragment_bottomsheet_others.xml │ │ ├── fragment_cafeteria.xml │ │ ├── fragment_map.xml │ │ ├── fragment_menu.xml │ │ ├── fragment_my_page.xml │ │ ├── item_cafeteria_section.xml │ │ ├── item_calendar_list.xml │ │ ├── item_menu.xml │ │ ├── item_menu_name.xml │ │ ├── item_menu_pick.xml │ │ └── item_review.xml │ │ ├── menu │ │ ├── menu_bottom_navigation.xml │ │ ├── menu_main.xml │ │ ├── menu_my_review.xml │ │ └── menu_other_review.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── eatssu_navigation.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── array.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ ├── firebase_remote_config.xml │ │ ├── network_security_config.xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── com │ └── eatssu │ └── android │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | type: Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | # ⚠️ Bug Report 11 | 12 | ## 발견한 문제 13 | 14 | > 정확한 문제와 상황을 적어주세요. 나중에 개발자가 다시 시연하기 편하게 상황까지 적어주시면 좋습니다. 15 | 16 | 17 | 18 | ## 스크린샷 19 | 20 | > 텍스트로 설명하기 어렵거나 편하게 이해하기 위해서는 발생한 문제의 스크린샷이나 동영상을 남겨주세요. 21 | 22 | 23 | 24 | 25 | ## 플랫폼(Android) 26 | 27 | - 디바이스: 28 | 29 | ## 기타 정보 30 | 31 | - 앱 버전: 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature" 3 | about: Develop new feature 4 | title: '' 5 | labels: '' 6 | type: Feature 7 | assignees: '' 8 | 9 | --- 10 | 11 | ## 목적 12 | 13 | > 14 | 15 | ## 작업 상세 내용 16 | 17 | - [ ] 18 | 19 | ## 참고사항 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | 5 | ## Describe your changes 6 | 7 | 8 | ## Issue 9 | 10 | - Resolves # 11 | 12 | ## To reviewers 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "develop" ] 6 | pull_request: 7 | branches: [ "develop" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Cache Gradle packages 18 | uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.gradle/caches 22 | ~/.gradle/wrapper 23 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/buildSrc/**/*.kt') }} 24 | restore-keys: | 25 | ${{ runner.os }}-gradle- 26 | 27 | - name: set up JDK 17 28 | uses: actions/setup-java@v3 29 | with: 30 | java-version: '17' 31 | distribution: 'temurin' 32 | cache: gradle 33 | 34 | - name: Create Local Properties 35 | run: touch local.properties 36 | 37 | - name: Access Local Properties 38 | env: 39 | DEV_BASE_URL: ${{ secrets.DEV_BASE_URL }} 40 | PROD_BASE_URL: ${{ secrets.PROD_BASE_URL }} 41 | KAKAO_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }} 42 | run: | 43 | echo DEV_BASE_URL=\"$DEV_BASE_URL\" >> local.properties 44 | echo PROD_BASE_URL=\"$PROD_BASE_URL\" >> local.properties 45 | echo KAKAO_NATIVE_APP_KEY=$KAKAO_NATIVE_APP_KEY >> local.properties 46 | 47 | - name: Generate google-services.json 48 | run: | 49 | echo "$GOOGLE_SERVICE" > app/google-services.json.b64 50 | base64 -d -i app/google-services.json.b64 > app/google-services.json 51 | env: 52 | GOOGLE_SERVICE: ${{ secrets.GOOGLE_SERVICE }} 53 | 54 | - name: Grant execute permission for gradlew 55 | run: chmod +x gradlew 56 | - name: Build with Gradle 57 | run: ./gradlew build 58 | -------------------------------------------------------------------------------- /.github/workflows/release_tag.yml: -------------------------------------------------------------------------------- 1 | name: Release Tag 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | branches: 7 | - develop # develop 브랜치로의 PR만 대상, 나중에 master나 prodution으로 바꾸는게 좋을듯 8 | 9 | jobs: 10 | on-merge: 11 | if: | 12 | github.event.pull_request.merged == true && 13 | startsWith(github.event.pull_request.head.ref, 'release/') 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Extract version #브랜치명에서 버전 정보 추출 20 | id: extract_version 21 | run: | 22 | VERSION=$(echo "${{ github.event.pull_request.head.ref }}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') 23 | echo "version=$VERSION" >> $GITHUB_OUTPUT 24 | 25 | - name: Create Release 26 | uses: actions/create-release@v1 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | tag_name: ${{ steps.extract_version.outputs.version }} 31 | release_name: ${{ steps.extract_version.outputs.version }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build 23 | 24 | # release 25 | /app/release/ 26 | 27 | # Local configuration file (sdk path, etc) -> APP_KEY 포함 28 | /build 29 | local.properties 30 | app/src/main/res/values/kakao_string.xml 31 | app/google-services.json 32 | 33 | # Proguard folder generated by Eclipse 34 | proguard/ 35 | 36 | # Log Files 37 | *.log 38 | 39 | # Android Studio Navigation editor temp files 40 | .navigation/ 41 | 42 | # Android Studio captures folder 43 | captures/ 44 | 45 | # IntelliJ 46 | *.iml 47 | .idea/ 48 | .idea/* 49 | .idea/compiler.xml 50 | .idea/misc.xml 51 | .idea/workspace.xml 52 | .idea/tasks.xml 53 | .idea/gradle.xml 54 | .idea/assetWizardSettings.xml 55 | .idea/dictionaries 56 | .idea/libraries 57 | .idea/deploymentTargetDropDown.xml 58 | # Android Studio 3 in .gitignore file. 59 | .idea/caches 60 | .idea/modules.xml 61 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 62 | .idea/navEditor.xml 63 | 64 | # Keystore files 65 | # Uncomment the following lines if you do not want to check your keystore files in. 66 | #*.jks 67 | #*.keystore 68 | 69 | # External native build folder generated in Android Studio 2.2 and later 70 | .externalNativeBuild 71 | .cxx/ 72 | 73 | # Google Services (e.g. APIs or Firebase) 74 | # google-services.json 75 | 76 | # Freeline 77 | freeline.py 78 | freeline/ 79 | freeline_project_description.json 80 | 81 | # fastlane 82 | fastlane/report.xml 83 | fastlane/Preview.html 84 | fastlane/screenshots 85 | fastlane/test_output 86 | fastlane/readme.md 87 | 88 | # Version control 89 | vcs.xml 90 | 91 | # lint 92 | lint/intermediates/ 93 | lint/generated/ 94 | lint/outputs/ 95 | lint/tmp/ 96 | # lint/reports/ 97 | 98 | .DS_Store 99 | ._.DS_Store 100 | **/.DS_Store 101 | **/._.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 EAT-SSU 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 숭실대에서 먹자, 잇슈! 2 | - 숭실대 학식 리뷰 앱 3 | - 기간: 2023.03 ~ 4 | - [PlayStore](https://play.google.com/store/apps/details?id=com.eatssu.android) 출시일 2023.11.28 ~ 5 | - [EAT-SSU 소개 Notion](https://eat-ssu.notion.site/EAT-SSU-1d2eeef75a1681198583e5282eaae6ba) 6 | - [리쿠르팅](https://eat-ssu.notion.site/1d2eeef75a1681ae800cf6ffa6faa37d) 7 | 8 | ## 기여자 9 | |[유진](https://github.com/HI-JIN2)|[이현지](https://github.com/Amepistheo)|[강유리](https://github.com/kangyuri1114)| 10 | |:---:|:---:|:---:| 11 | |![image](https://github.com/user-attachments/assets/aa023f64-60e1-4d6c-8ea2-35d75cc167f9)|![image](https://github.com/user-attachments/assets/3ecf5e16-35ea-452f-96b0-fa4479d7133c)|![image](https://github.com/user-attachments/assets/a1caa937-4cfe-4d19-a2f2-8335f12595d9)| 12 | |2022.11~현재|2022.11~2024.02|2025.02~현재| 13 | 14 | ![그래픽이미지](https://github.com/user-attachments/assets/e89f46bb-dece-45a9-a453-a00bf9d463cd) 15 | 16 | 17 | 18 | 19 | ## 🛠 Tech Stack 20 | - Kotlin 21 | - MVVM 22 | - Clean Architecture 23 | - Coroutine + Flow 24 | - UiState 25 | - Hilt 26 | - xml + viewBinding (+dataBinding) 27 | - Retrofit2 + Okhttp3 28 | - Gilde 29 | - KaKao OAuth SDK 30 | - Firebase RemoteConfig, Crashlytics 31 | 32 | ## 🤔 Not Yet.. 33 | - Modularization 34 | - Jetpack Compose 35 | - DataSource + Repository Pattern 36 | 37 | ## 📄 Package 38 | ``` 39 | 📦com.eatssu.android 40 | ├── 📂alarm 41 | ├── 📂data 42 | │ ├── 📂dto 43 | │ │ ├── 📂request 44 | │ │ └── 📂response 45 | │ ├── 📂enums 46 | │ ├── 📂repository(impl) 47 | │ └── 📂service 48 | ├── 📂di 49 | ├── 📂domain 50 | │ ├── 📂model 51 | │ ├── 📂repository 52 | │ └── 📂usecase 53 | ├── 📂presentation 54 | │ ├── 📂base 55 | │ ├── 📂common 56 | │ ├── 📂feature 57 | │ │ └── 📂... 58 | │ └── 📂util 59 | └── 📄App 60 | ``` 61 | 62 | 63 | ## 🤖 Android 64 | - Android Studio : Android Studio Koala | 2024.1.1 65 | - JDK : 17 66 | - minSDK : 23 67 | - targetSDK : 34 68 | 69 | ## 🐚 Convertion 70 | - [Android Convention Docs](https://github.com/EAT-SSU/Android/wiki/Android-convention) 71 | - [Git Convention Docs](https://github.com/EAT-SSU/Android/wiki/Git-convention) 72 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | kakao_string.xml 3 | google-services.json -------------------------------------------------------------------------------- /app/libs/libDaumMapAndroid.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/libs/libDaumMapAndroid.jar -------------------------------------------------------------------------------- /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.kts. 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 | # https://developers.kakao.com/docs/latest/en/getting-started/sdk-android#configure-for-shrinking-and-obfuscation-(optional) 24 | -keep class com.kakao.sdk.**.model.* { ; } 25 | -keep class * extends com.google.gson.TypeAdapter 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/eatssu/android/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android 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.eatssu.android", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/App.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.eatssu.android.domain.model.TokenState 6 | import com.eatssu.android.domain.model.TokenStateManager 7 | import com.eatssu.android.presentation.base.TokenEventBus 8 | import com.google.firebase.FirebaseApp 9 | import com.google.firebase.analytics.ktx.analytics 10 | import com.google.firebase.crashlytics.FirebaseCrashlytics 11 | import com.google.firebase.ktx.Firebase 12 | import com.kakao.sdk.common.KakaoSdk 13 | import dagger.hilt.android.HiltAndroidApp 14 | import kotlinx.coroutines.CoroutineScope 15 | import kotlinx.coroutines.Dispatchers 16 | import kotlinx.coroutines.SupervisorJob 17 | import kotlinx.coroutines.launch 18 | import timber.log.Timber 19 | 20 | /** App: 앱이 살아있는 동안 공통 리소스 관리를 위한 클래스 */ 21 | @HiltAndroidApp 22 | class App: Application() { 23 | companion object{ 24 | lateinit var appContext: Context //todo 이거 빼기 25 | } 26 | 27 | /** 앱 전체에서 사용할 수 있는 CoroutineScope(독립적인 공간을 만들어 안정성 높임) 28 | * 자식 CoroutineScope가 취소되더라도 부모 CoroutineScope는 취소되지 않음 29 | * */ 30 | private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) 31 | 32 | override fun onCreate() { 33 | super.onCreate() 34 | FirebaseApp.initializeApp(this) 35 | 36 | appContext = this 37 | KakaoSdk.init(this,BuildConfig.KAKAO_NATIVE_APP_KEY) 38 | 39 | if (BuildConfig.DEBUG) { 40 | Timber.plant(Timber.DebugTree()) 41 | FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false) 42 | Firebase.analytics.setAnalyticsCollectionEnabled(false) 43 | } else { 44 | FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) 45 | Firebase.analytics.setAnalyticsCollectionEnabled(true) 46 | } 47 | 48 | collectTokenState() 49 | } 50 | 51 | /** 토큰 상태를 application에서 감지하여 TokenEventBus에 전달 */ 52 | private fun collectTokenState(){ 53 | appScope.launch { 54 | TokenStateManager.state.collect { state -> 55 | if (state == TokenState.EXPIRED) { 56 | TokenEventBus.notifyTokenExpired() 57 | } else if(state == TokenState.ERROR) { 58 | TokenEventBus.notifyServerError() 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/ChangeNicknameRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | data class ChangeNicknameRequest( 4 | val nickname: String, 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/ChangePwRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | data class ChangePwRequest( 4 | val pwd: String, 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/CheckValidTokenRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | data class CheckValidTokenRequest( 4 | val token: String, 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/LoginWithKakaoRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class LoginWithKakaoRequest( 6 | @SerializedName("email") 7 | val email: String, 8 | 9 | @SerializedName("providerId") 10 | val providerId: String, 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/ModifyReviewRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ModifyReviewRequest( 6 | @SerializedName("mainRating") var mainRating: Int? = null, 7 | @SerializedName("amountRating") var amountRating: Int? = null, 8 | @SerializedName("tasteRating") var tasteRating: Int? = null, 9 | @SerializedName("content") var content: String? = null, 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/ReportRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ReportRequest( 6 | @SerializedName("reviewId") 7 | val reviewId: Long, 8 | 9 | @SerializedName("reportType") 10 | val reportType: String, 11 | 12 | @SerializedName("content") 13 | val content: String, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/request/WriteReviewRequest.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.request 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class WriteReviewRequest( 6 | 7 | @SerializedName("mainRating") var mainRating: Int? = null, 8 | @SerializedName("amountRating") var amountRating: Int? = null, 9 | @SerializedName("tasteRating") var tasteRating: Int? = null, 10 | @SerializedName("content") var content: String? = null, 11 | @SerializedName("imageUrl") var imageUrl: String? = null, 12 | 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class BaseResponse( 6 | @SerializedName("isSuccess") var isSuccess: Boolean? = null, 7 | @SerializedName("code") var code: Int? = null, 8 | @SerializedName("message") var message: String? = null, 9 | @SerializedName("result") var result: T? = null, 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/ImageResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ImageResponse( 6 | @SerializedName("url") var url: String? = null, 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MealResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.Menu 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GetMealResponse( 7 | 8 | @SerializedName("mealId") var mealId: Long? = null, 9 | @SerializedName("price") var price: Int? = null, 10 | @SerializedName("rating") var rating: Double? = null, 11 | @SerializedName("briefMenus") var briefMenus: ArrayList = arrayListOf(), 12 | ) 13 | 14 | data class MenusInformationList( 15 | 16 | @SerializedName("menuId") var menuId: Long? = null, 17 | @SerializedName("name") var name: String? = null, 18 | 19 | ) 20 | 21 | fun ArrayList.mapTodayMenuResponseToMenu(): List { 22 | val menuList = mutableListOf() 23 | 24 | this.forEach { mealResponse -> 25 | val menuNames = 26 | mealResponse.briefMenus.joinToString(separator = "+") { it.name ?: "" } 27 | val mealId = mealResponse.mealId ?: -1 28 | val price = mealResponse.price ?: 0 29 | val mainRating = mealResponse.rating ?: 0.0 30 | 31 | val menu = Menu(mealId, menuNames, price, mainRating) 32 | 33 | menuList.add(menu) 34 | } 35 | 36 | return menuList 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MealReviewInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.ReviewInfo 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GetMealReviewInfoResponse( 7 | 8 | @SerializedName("menuNames") var menuNames: ArrayList = arrayListOf(), 9 | @SerializedName("totalReviewCount") var totalReviewCount: Int? = null, 10 | @SerializedName("mainRating") var mainRating: Double? = null, 11 | @SerializedName("amountRating") var amountRating: Double? = null, 12 | @SerializedName("tasteRating") var tasteRating: Double? = null, 13 | @SerializedName("reviewRatingCount") var reviewRatingCount: ReviewRatingCount = ReviewRatingCount(), 14 | 15 | ) { 16 | data class ReviewRatingCount( 17 | 18 | @SerializedName("oneStarCount") var oneStarCount: Int? = null, 19 | @SerializedName("twoStarCount") var twoStarCount: Int? = null, 20 | @SerializedName("threeStarCount") var threeStarCount: Int? = null, 21 | @SerializedName("fourStarCount") var fourStarCount: Int? = null, 22 | @SerializedName("fiveStarCount") var fiveStarCount: Int? = null, 23 | 24 | ) 25 | 26 | } 27 | 28 | fun GetMealReviewInfoResponse.asReviewInfo() = ReviewInfo( 29 | 30 | name = menuNames.joinToString(separator = "+"), 31 | reviewCnt = totalReviewCount ?: 0, 32 | mainRating = mainRating ?: 0.0, 33 | amountRating = amountRating ?: 0.0, 34 | tasteRating = tasteRating ?: 0.0, 35 | one = reviewRatingCount.oneStarCount ?: 0, 36 | two = reviewRatingCount.twoStarCount ?: 0, 37 | three = reviewRatingCount.threeStarCount ?: 0, 38 | four = reviewRatingCount.fourStarCount ?: 0, 39 | five = reviewRatingCount.fiveStarCount ?: 0, 40 | ) 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MenuOfMealResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.MenuMini 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class MenuOfMealResponse( 7 | @SerializedName("briefMenus") var briefMenus: ArrayList = arrayListOf(), 8 | ) 9 | 10 | data class MenusInformation( 11 | 12 | @SerializedName("menuId") var menuId: Long, 13 | @SerializedName("name") var name: String, 14 | 15 | ) 16 | 17 | fun MenuOfMealResponse.toMenuMiniList(): List { 18 | return briefMenus.map { it.toMenuMini() } 19 | } 20 | 21 | fun MenusInformation.toMenuMini(): MenuMini { 22 | return MenuMini( 23 | id = this.menuId, 24 | name = this.name 25 | ) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MenuResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.Menu 4 | import com.google.gson.annotations.SerializedName 5 | import timber.log.Timber 6 | 7 | 8 | data class GetFixedMenuResponse( 9 | 10 | @SerializedName("categoryMenuListCollection") var categoryMenuListCollection: ArrayList = arrayListOf(), 11 | 12 | ) 13 | 14 | data class CategoryMenuListCollection( 15 | 16 | @SerializedName("category") var category: String? = null, 17 | @SerializedName("menus") var menus: ArrayList = arrayListOf(), 18 | 19 | ) 20 | 21 | data class MenuInformationList( 22 | 23 | @SerializedName("menuId") var menuId: Long? = null, 24 | @SerializedName("name") var name: String? = null, 25 | @SerializedName("price") var price: Int? = null, 26 | @SerializedName("rating") var rating: Double? = null, 27 | 28 | ) 29 | 30 | 31 | fun GetFixedMenuResponse.mapFixedMenuResponseToMenu(): List { 32 | val menus = mutableListOf() 33 | 34 | categoryMenuListCollection.forEach { categoryMenuList -> 35 | val categoryName = categoryMenuList.category ?: "" 36 | categoryMenuList.menus.forEach { menuInfo -> 37 | val menu = Menu( 38 | id = menuInfo.menuId ?: 0, 39 | name = menuInfo.name ?: "", 40 | price = menuInfo.price ?: 0, 41 | rate = menuInfo.rating ?: 0.0 42 | ) 43 | menus.add(menu) 44 | } 45 | } 46 | Timber.d(menus.toString()) 47 | 48 | return menus 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MenuReviewInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.ReviewInfo 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GetMenuReviewInfoResponse( 7 | 8 | @SerializedName("menuName") var menuName: String, 9 | @SerializedName("totalReviewCount") var totalReviewCount: Int, 10 | @SerializedName("mainRating") var mainRating: Double? = null, 11 | @SerializedName("amountRating") var amountRating: Double? = null, 12 | @SerializedName("tasteRating") var tasteRating: Double? = null, 13 | @SerializedName("reviewRatingCount") var reviewRatingCount: ReviewRatingCount, 14 | ) { 15 | data class ReviewRatingCount( 16 | 17 | @SerializedName("oneStarCount") var oneStarCount: Int, 18 | @SerializedName("twoStarCount") var twoStarCount: Int, 19 | @SerializedName("threeStarCount") var threeStarCount: Int, 20 | @SerializedName("fourStarCount") var fourStarCount: Int, 21 | @SerializedName("fiveStarCount") var fiveStarCount: Int, 22 | 23 | ) 24 | 25 | } 26 | 27 | fun GetMenuReviewInfoResponse.asReviewInfo() = ReviewInfo( 28 | 29 | name = menuName, 30 | reviewCnt = totalReviewCount, 31 | mainRating = mainRating ?: 0.0, 32 | amountRating = amountRating ?: 0.0, 33 | tasteRating = tasteRating ?: 0.0, 34 | one = reviewRatingCount.oneStarCount, 35 | two = reviewRatingCount.twoStarCount, 36 | three = reviewRatingCount.threeStarCount, 37 | four = reviewRatingCount.fourStarCount, 38 | five = reviewRatingCount.fiveStarCount, 39 | 40 | ) 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MyInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class MyInfoResponse( 6 | 7 | @SerializedName("nickname") var nickname: String? = null, 8 | @SerializedName("provider") var provider: String, 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/MyReviewResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.Review 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class MyReviewResponse( 7 | @SerializedName("numberOfElements") var numberOfElements: Int? = null, 8 | @SerializedName("hasNext") var hasNext: Boolean? = null, 9 | @SerializedName("dataList") var dataList: List, 10 | 11 | ) { 12 | data class DataList( 13 | @SerializedName("reviewId") var reviewId: Long, 14 | @SerializedName("mainRating") var mainRating: Int, 15 | @SerializedName("amountRating") var amountRating: Int, 16 | @SerializedName("tasteRating") var tasteRating: Int, 17 | @SerializedName("writeDate") var writeDate: String, 18 | @SerializedName("menuName") var menuName: String, 19 | @SerializedName("content") var content: String, 20 | @SerializedName("imgUrlList") var imgUrlList: ArrayList? = arrayListOf(), 21 | 22 | ) 23 | } 24 | 25 | fun MyReviewResponse.toReviewList(): List { 26 | return dataList.map { data -> 27 | Review( 28 | reviewId = data.reviewId, 29 | isWriter = true, 30 | menu = data.menuName, 31 | writerNickname = "", 32 | mainGrade = data.mainRating, 33 | amountGrade = data.amountRating, 34 | tasteGrade = data.tasteRating, 35 | writeDate = data.writeDate, 36 | content = data.content, 37 | imgUrl = data.imgUrlList 38 | ) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/ReviewListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.eatssu.android.domain.model.Review 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GetReviewListResponse( 7 | @SerializedName("numberOfElements") var numberOfElements: Int? = null, 8 | @SerializedName("hasNext") var hasNext: Boolean? = null, 9 | @SerializedName("dataList") var dataList: ArrayList? = arrayListOf(), 10 | ) { 11 | 12 | data class DataList( 13 | 14 | @SerializedName("reviewId") var reviewId: Long, 15 | @SerializedName("menu") var menu: String, 16 | @SerializedName("writerId") var writerId: Long, 17 | @SerializedName("isWriter") var isWriter: Boolean, 18 | @SerializedName("writerNickname") var writerNickname: String?, 19 | @SerializedName("mainRating") var mainRating: Int, 20 | @SerializedName("amountRating") var amountRating: Int, 21 | @SerializedName("tasteRating") var tasteRating: Int, 22 | @SerializedName("writedAt") var writedAt: String, 23 | @SerializedName("content") var content: String, 24 | @SerializedName("imageUrls") var imageUrls: ArrayList? = arrayListOf(), 25 | 26 | ) 27 | } 28 | 29 | fun GetReviewListResponse.toReviewList(): List { 30 | return dataList!!.map { data -> 31 | Review( 32 | reviewId = data.reviewId, 33 | isWriter = data.isWriter, 34 | menu = data.menu, 35 | writerNickname = data.writerNickname ?: "유저", 36 | mainGrade = data.mainRating, 37 | amountGrade = data.amountRating, 38 | tasteGrade = data.tasteRating, 39 | writeDate = data.writedAt, 40 | content = data.content, 41 | imgUrl = data.imageUrls 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/dto/response/TokenResponse.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.dto.response 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class TokenResponse( 6 | @SerializedName("accessToken") 7 | val accessToken: String, 8 | 9 | @SerializedName("refreshToken") 10 | val refreshToken: String, 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/enums/MenuType.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.enums 2 | 3 | enum class MenuType (val displayName: String){ 4 | FIXED("고정메뉴"), 5 | VARIABLE("가변메뉴") 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/enums/Provider.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.enums 2 | 3 | enum class Provider { 4 | KAKAO 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/enums/ReportType.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.enums 2 | 3 | import com.eatssu.android.R 4 | 5 | enum class ReportType(val description: Int) { 6 | NO_ASSOCIATE_CONTENT(R.string.report1), 7 | IMPROPER_CONTENT(R.string.report2), 8 | IMPROPER_ADVERTISEMENT(R.string.report3), 9 | COPY(R.string.report4), 10 | COPYRIGHT(R.string.report5), 11 | EXTRA(R.string.report6) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/enums/Restaurant.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.enums 2 | 3 | enum class Restaurant(val displayName: String, val menuType: MenuType) { 4 | HAKSIK("학생 식당", MenuType.VARIABLE), 5 | DODAM("도담 식당", MenuType.VARIABLE), 6 | DORMITORY("기숙사 식당", MenuType.VARIABLE), 7 | FOOD_COURT("푸드 코트", MenuType.FIXED), 8 | SNACK_CORNER("스낵 코너", MenuType.FIXED), 9 | THE_KITCHEN("더 키친", MenuType.FIXED) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/enums/Time.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.enums 2 | 3 | enum class Time { 4 | MORNING, LUNCH, DINNER 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/repository/MealRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.repository 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.MenuOfMealResponse 5 | import com.eatssu.android.data.service.MealService 6 | import com.eatssu.android.domain.repository.MealRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import javax.inject.Inject 10 | 11 | class MealRepositoryImpl @Inject constructor(private val mealService: MealService) : 12 | MealRepository { 13 | 14 | // override suspend fun getTodayMeal( 15 | // date: String, 16 | // restaurant: String, 17 | // time: String 18 | // ): Flow>> = 19 | // flow { emit(mealService.getTodayMeal(date, restaurant, time)) } 20 | 21 | override suspend fun getMenuInfoByMealId(mealId: Long): Flow> = 22 | flow { 23 | emit(mealService.getMenuInfoByMealId(mealId)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/repository/OauthRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.repository 2 | 3 | import com.eatssu.android.data.dto.request.CheckValidTokenRequest 4 | import com.eatssu.android.data.dto.request.LoginWithKakaoRequest 5 | import com.eatssu.android.data.dto.response.BaseResponse 6 | import com.eatssu.android.data.dto.response.TokenResponse 7 | import com.eatssu.android.data.service.OauthService 8 | import com.eatssu.android.domain.repository.OauthRepository 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.flow 11 | import javax.inject.Inject 12 | 13 | class OauthRepositoryImpl @Inject constructor(private val oauthService: OauthService) : 14 | OauthRepository { 15 | override suspend fun reissueToken(refreshToken: String): Flow> = 16 | flow { 17 | emit(oauthService.getNewToken(refreshToken)) 18 | } 19 | 20 | 21 | override suspend fun login(body: LoginWithKakaoRequest): Flow> = 22 | flow { 23 | emit(oauthService.loginWithKakao(body)) 24 | } 25 | 26 | override suspend fun checkValidToken(body: CheckValidTokenRequest): Flow> = 27 | flow { 28 | emit(oauthService.checkValidToken(body)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/repository/PreferencesRepository.kt: -------------------------------------------------------------------------------- 1 | // PreferencesRepository.kt 2 | package com.eatssu.android.data.repository 3 | 4 | import android.content.Context 5 | import androidx.datastore.core.DataStore 6 | import androidx.datastore.preferences.core.Preferences 7 | import androidx.datastore.preferences.core.booleanPreferencesKey 8 | import androidx.datastore.preferences.core.edit 9 | import androidx.datastore.preferences.preferencesDataStore 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.map 12 | 13 | class PreferencesRepository(private val context: Context) { 14 | 15 | private val Context.dataStore: DataStore by preferencesDataStore(name = "settings") 16 | 17 | companion object { 18 | private val DAILY_NOTIFICATION_KEY = booleanPreferencesKey("daily_notification") 19 | } 20 | 21 | val dailyNotificationStatus: Flow = context.dataStore.data 22 | .map { preferences -> 23 | preferences[DAILY_NOTIFICATION_KEY] ?: false // Default value is false 24 | } 25 | 26 | suspend fun setDailyNotificationStatus(status: Boolean) { 27 | context.dataStore.edit { preferences -> 28 | preferences[DAILY_NOTIFICATION_KEY] = status 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/repository/ReportRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.repository 2 | 3 | import com.eatssu.android.data.dto.request.ReportRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.data.service.ReportService 6 | import com.eatssu.android.domain.repository.ReportRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import javax.inject.Inject 10 | 11 | class ReportRepositoryImpl @Inject constructor(private val reportService: ReportService) : 12 | ReportRepository { 13 | 14 | override suspend fun reportReview(body: ReportRequest): Flow> = 15 | flow { 16 | emit(reportService.reportReview(body)) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/repository/ReviewRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.repository 2 | 3 | import com.eatssu.android.data.dto.request.ModifyReviewRequest 4 | import com.eatssu.android.data.dto.request.WriteReviewRequest 5 | import com.eatssu.android.data.dto.response.BaseResponse 6 | import com.eatssu.android.data.dto.response.GetMealReviewInfoResponse 7 | import com.eatssu.android.data.dto.response.GetMenuReviewInfoResponse 8 | import com.eatssu.android.data.dto.response.GetReviewListResponse 9 | import com.eatssu.android.data.dto.response.ImageResponse 10 | import com.eatssu.android.data.service.ReviewService 11 | import com.eatssu.android.domain.repository.ReviewRepository 12 | import kotlinx.coroutines.flow.Flow 13 | import kotlinx.coroutines.flow.flow 14 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 15 | import okhttp3.MultipartBody 16 | import okhttp3.RequestBody.Companion.asRequestBody 17 | import java.io.File 18 | import javax.inject.Inject 19 | 20 | class ReviewRepositoryImpl @Inject constructor(private val reviewService: ReviewService) : 21 | ReviewRepository { 22 | 23 | override suspend fun writeReview( 24 | menuId: Long, 25 | body: WriteReviewRequest, 26 | ): Flow> = 27 | flow { 28 | emit(reviewService.writeReview(menuId, body)) 29 | } 30 | 31 | override suspend fun deleteReview(reviewId: Long): Flow> = 32 | flow { 33 | emit(reviewService.deleteReview(reviewId)) 34 | } 35 | 36 | override suspend fun modifyReview( 37 | reviewId: Long, 38 | body: ModifyReviewRequest, 39 | ): Flow> = 40 | flow { 41 | emit(reviewService.modifyReview(reviewId, body)) 42 | } 43 | 44 | override suspend fun getReviewList( 45 | menuType: String, 46 | mealId: Long?, 47 | menuId: Long?, 48 | ): Flow> = flow { 49 | emit(reviewService.getReviewList(menuType, mealId, menuId)) 50 | } 51 | 52 | override suspend fun getMenuReviewInfo(menuId: Long): Flow> = 53 | flow { 54 | emit(reviewService.getMenuReviewInfo(menuId)) 55 | } 56 | 57 | override suspend fun getMealReviewInfo(mealId: Long): Flow> = 58 | flow { 59 | emit(reviewService.getMealReviewInfo(mealId)) 60 | } 61 | override suspend fun getImageString(file: File): Flow> = flow { 62 | val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull()) 63 | val multipart = MultipartBody.Part.createFormData("image", file.name, requestFile) 64 | val response = reviewService.uploadImage(multipart) 65 | emit(response) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/repository/UserRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.repository 2 | 3 | import com.eatssu.android.data.dto.request.ChangeNicknameRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.data.dto.response.MyInfoResponse 6 | import com.eatssu.android.data.dto.response.MyReviewResponse 7 | import com.eatssu.android.data.service.UserService 8 | import com.eatssu.android.domain.repository.UserRepository 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.flow 11 | import javax.inject.Inject 12 | 13 | class UserRepositoryImpl @Inject constructor(private val userService: UserService) : 14 | UserRepository { 15 | 16 | override suspend fun updateUserName(body: ChangeNicknameRequest): Flow> = 17 | flow { 18 | emit(userService.changeNickname(body)) 19 | } 20 | 21 | 22 | override suspend fun checkUserNameValidation(nickname: String): Flow> = 23 | flow { 24 | emit(userService.checkNickname(nickname)) 25 | } 26 | 27 | override suspend fun getUserReviews(): Flow> = 28 | flow { 29 | emit(userService.getMyReviews()) 30 | } 31 | 32 | override suspend fun getUserInfo(): Flow> = 33 | flow { 34 | emit(userService.getMyInfo()) 35 | } 36 | 37 | override suspend fun signOut(): Flow> = 38 | flow { 39 | emit(userService.signOut()) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/service/MealService.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.service 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.GetMealResponse 5 | import com.eatssu.android.data.dto.response.MenuOfMealResponse 6 | import retrofit2.Call 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | import retrofit2.http.Query 10 | 11 | interface MealService { 12 | /** 13 | * 변동메뉴 식단 리스트 조회 By 식당 14 | */ 15 | @GET("meals") 16 | fun getTodayMeal( 17 | @Query("date") date: String, 18 | @Query("restaurant") restaurant: String, 19 | @Query("time") time: String, 20 | ): Call>> 21 | 22 | /** 23 | * 메뉴 정보 리스트 조회 24 | */ 25 | @GET("meals/{mealId}/menus-info") 26 | suspend fun getMenuInfoByMealId( 27 | @Path("mealId") mealId: Long, 28 | ): BaseResponse 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/service/MenuService.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.service 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.GetFixedMenuResponse 5 | import retrofit2.Call 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | interface MenuService { 10 | 11 | /** 12 | * 고정 메뉴 리스트 조회 13 | */ 14 | @GET("menus") 15 | fun getFixMenu( 16 | @Query("restaurant") restaurant: String, 17 | ): Call> 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/service/OauthService.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.service 2 | 3 | import com.eatssu.android.data.dto.request.CheckValidTokenRequest 4 | import com.eatssu.android.data.dto.request.LoginWithKakaoRequest 5 | import com.eatssu.android.data.dto.response.BaseResponse 6 | import com.eatssu.android.data.dto.response.TokenResponse 7 | import retrofit2.http.Body 8 | import retrofit2.http.Header 9 | import retrofit2.http.POST 10 | 11 | interface OauthService { //여기는 토큰이 없는 레트로핏을 끼웁니다. 12 | @POST("oauths/reissue/token") //accessToken, refreshToken 재발급 13 | suspend fun getNewToken( 14 | @Header("Authorization") refreshToken: String?, 15 | ) //얘는 SP에 있는거 헤더에 넣어주면 됩니다. 16 | : BaseResponse 17 | 18 | @POST("oauths/kakao") 19 | suspend fun loginWithKakao( 20 | @Body request: LoginWithKakaoRequest, 21 | ): BaseResponse 22 | 23 | @POST("oauths/valid/token") 24 | suspend fun checkValidToken( 25 | @Body request: CheckValidTokenRequest, 26 | ): BaseResponse 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/service/ReportService.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.service 2 | 3 | import com.eatssu.android.data.dto.request.ReportRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import retrofit2.http.Body 6 | import retrofit2.http.POST 7 | 8 | interface ReportService { 9 | @POST("reports") //리뷰 신고하기 10 | suspend fun reportReview( 11 | @Body request: ReportRequest, 12 | ): BaseResponse 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/service/ReviewService.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.service 2 | 3 | 4 | import com.eatssu.android.data.dto.request.ModifyReviewRequest 5 | import com.eatssu.android.data.dto.request.WriteReviewRequest 6 | import com.eatssu.android.data.dto.response.BaseResponse 7 | import com.eatssu.android.data.dto.response.GetMealReviewInfoResponse 8 | import com.eatssu.android.data.dto.response.GetMenuReviewInfoResponse 9 | import com.eatssu.android.data.dto.response.GetReviewListResponse 10 | import com.eatssu.android.data.dto.response.ImageResponse 11 | import okhttp3.MultipartBody 12 | import retrofit2.http.Body 13 | import retrofit2.http.DELETE 14 | import retrofit2.http.GET 15 | import retrofit2.http.Multipart 16 | import retrofit2.http.PATCH 17 | import retrofit2.http.POST 18 | import retrofit2.http.Part 19 | import retrofit2.http.Path 20 | import retrofit2.http.Query 21 | 22 | 23 | interface ReviewService { 24 | @POST("/reviews/write/{menuId}") //리뷰 작성 25 | suspend fun writeReview( 26 | @Path("menuId") menuId: Long, 27 | @Body request: WriteReviewRequest, 28 | ): BaseResponse 29 | 30 | @DELETE("/reviews/{reviewId}") //리뷰 삭제 31 | suspend fun deleteReview( 32 | @Path("reviewId") reviewId: Long, 33 | ): BaseResponse 34 | 35 | @PATCH("/reviews/{reviewId}") //리뷰 수정(글 수정) 36 | suspend fun modifyReview( 37 | @Path("reviewId") reviewId: Long, 38 | @Body request: ModifyReviewRequest, 39 | ): BaseResponse 40 | 41 | //Todo paging 라이브러리 써보기 42 | @GET("/reviews") //리뷰 리스트 조회 43 | suspend fun getReviewList( 44 | @Query("menuType") menuType: String, 45 | @Query("mealId") mealId: Long?, 46 | @Query("menuId") menuId: Long?, 47 | // @Query("lastReviewId") lastReviewId: Long?, 48 | @Query("page") page: Int? = 0, 49 | @Query("size") size: Int? = 20, 50 | @Query("sort") sort: List? = arrayListOf("date", "DESC"), 51 | ): BaseResponse 52 | 53 | @GET("/reviews/menus/{menuId}") //고정 메뉴 리뷰 정보 조회(메뉴명, 평점 등등) 54 | suspend fun getMenuReviewInfo( 55 | @Path("menuId") menuId: Long, 56 | ): BaseResponse 57 | 58 | @GET("/reviews/meals/{mealId}") //식단(변동 메뉴) 리뷰 정보 조회(메뉴명, 평점 등등) 59 | suspend fun getMealReviewInfo( 60 | @Path("mealId") mealId: Long, 61 | ): BaseResponse 62 | 63 | @Multipart 64 | @POST("/reviews/upload/image") //리뷰 이미지 업로드 65 | suspend fun uploadImage( 66 | @Part image: MultipartBody.Part, 67 | ): BaseResponse 68 | 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/data/service/UserService.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.data.service 2 | 3 | import com.eatssu.android.data.dto.request.ChangeNicknameRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.data.dto.response.MyInfoResponse 6 | import com.eatssu.android.data.dto.response.MyReviewResponse 7 | import retrofit2.http.Body 8 | import retrofit2.http.DELETE 9 | import retrofit2.http.GET 10 | import retrofit2.http.PATCH 11 | import retrofit2.http.Query 12 | 13 | interface UserService { 14 | 15 | @PATCH("users/nickname") //닉네임 수정 16 | suspend fun changeNickname( 17 | @Body request: ChangeNicknameRequest, 18 | ): BaseResponse 19 | 20 | @GET("users/validate/nickname") //닉네임 중복 체크 21 | suspend fun checkNickname( 22 | @Query("nickname") nickname: String, 23 | ): BaseResponse 24 | 25 | @GET("users/reviews") //내가 쓴 리뷰 모아보기 26 | suspend fun getMyReviews(): BaseResponse 27 | 28 | @GET("users/mypage") //내 정보 모아보기 29 | suspend fun getMyInfo(): BaseResponse 30 | 31 | @DELETE("users") //유저 탈퇴 32 | suspend fun signOut(): BaseResponse 33 | 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.di 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository 6 | import com.eatssu.android.data.repository.PreferencesRepository 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object AppModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideContext(application: Application): Context { 21 | return application.applicationContext 22 | } 23 | 24 | @Provides 25 | @Singleton 26 | fun providePreferencesRepository(@ApplicationContext context: Context): PreferencesRepository { 27 | return PreferencesRepository(context) 28 | } 29 | 30 | @Provides 31 | @Singleton 32 | fun provideFirebaseRemoteConfigRepository(): FirebaseRemoteConfigRepository { 33 | return FirebaseRemoteConfigRepository() 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/di/DataModule.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.di 2 | 3 | 4 | import com.eatssu.android.domain.repository.MealRepository 5 | import com.eatssu.android.data.repository.MealRepositoryImpl 6 | import com.eatssu.android.domain.repository.OauthRepository 7 | import com.eatssu.android.data.repository.OauthRepositoryImpl 8 | import com.eatssu.android.domain.repository.ReportRepository 9 | import com.eatssu.android.data.repository.ReportRepositoryImpl 10 | import com.eatssu.android.domain.repository.ReviewRepository 11 | import com.eatssu.android.domain.repository.UserRepository 12 | import com.eatssu.android.data.repository.UserRepositoryImpl 13 | import dagger.Binds 14 | import dagger.Module 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.components.SingletonComponent 17 | import com.eatssu.android.data.repository.ReviewRepositoryImpl as ReviewRepositoryImpl1 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | abstract class DataModule { 22 | 23 | @Binds 24 | internal abstract fun bindsOauthRepository( 25 | oauthRepositoryImpl: OauthRepositoryImpl, 26 | ): OauthRepository 27 | 28 | @Binds 29 | internal abstract fun bindsUserRepository( 30 | userRepositoryImpl: UserRepositoryImpl, 31 | ): UserRepository 32 | 33 | @Binds 34 | internal abstract fun bindsReportRepository( 35 | reportRepositoryImpl: ReportRepositoryImpl, 36 | ): ReportRepository 37 | 38 | @Binds 39 | internal abstract fun bindsReviewRepository( 40 | reviewRepositoryImpl: ReviewRepositoryImpl1, 41 | ): ReviewRepository 42 | 43 | @Binds 44 | internal abstract fun bindsMealRepository( 45 | mealRepositoryImpl: MealRepositoryImpl, 46 | ): MealRepository 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/di/ServiceModule.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.di 2 | 3 | import com.eatssu.android.data.service.MealService 4 | import com.eatssu.android.data.service.MenuService 5 | import com.eatssu.android.data.service.ReportService 6 | import com.eatssu.android.data.service.ReviewService 7 | import com.eatssu.android.data.service.UserService 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import retrofit2.Retrofit 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object ServiceModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideUserService(retrofit: Retrofit): UserService { 22 | return retrofit.create(UserService::class.java) 23 | } 24 | 25 | @Provides 26 | @Singleton 27 | fun provideReportService(retrofit: Retrofit): ReportService { 28 | return retrofit.create(ReportService::class.java) 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun provideReviewService(retrofit: Retrofit): ReviewService { 34 | return retrofit.create(ReviewService::class.java) 35 | } 36 | 37 | @Provides 38 | @Singleton 39 | fun provideMealService(retrofit: Retrofit): MealService { 40 | return retrofit.create(MealService::class.java) 41 | } 42 | 43 | @Provides 44 | @Singleton 45 | fun provideMenuService(retrofit: Retrofit): MenuService { 46 | return retrofit.create(MenuService::class.java) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/di/network/TokenInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.di.network 2 | 3 | 4 | import com.eatssu.android.domain.usecase.auth.GetAccessTokenUseCase 5 | import kotlinx.coroutines.runBlocking 6 | import okhttp3.Interceptor 7 | import okhttp3.Response 8 | import javax.inject.Inject 9 | 10 | /** 11 | * TokenInterceptor : API 요청 시 AccessToken을 헤더에 추가하는 인터셉터 12 | * */ 13 | class TokenInterceptor @Inject constructor( 14 | private val getAccessTokenUseCase: GetAccessTokenUseCase, 15 | ) : Interceptor { 16 | 17 | companion object { 18 | private const val HEADER_AUTHORIZATION = "Authorization" 19 | private const val HEADER_CONTENT_TYPE = "Content-Type" 20 | } 21 | 22 | override fun intercept(chain: Interceptor.Chain): Response { 23 | val accessToken = runBlocking { getAccessTokenUseCase() } 24 | val originalRequest = chain.request() 25 | 26 | val request = originalRequest.newBuilder() 27 | .addHeader(HEADER_CONTENT_TYPE, "application/json") 28 | .addHeader(HEADER_AUTHORIZATION, "Bearer $accessToken") 29 | .build() 30 | 31 | return chain.proceed(request) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/AndroidMessage.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | data class AndroidMessage( 4 | val dialog: Boolean, 5 | val message: String 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/CalendarData.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | data class CalendarData( 4 | var cl_date: String = "", // 날짜 5 | var cl_day: String = "" // 요일 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/Menu.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | data class Menu( 4 | val id: Long, 5 | val name: String, 6 | val price: Int, 7 | val rate: Double, 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/MenuMini.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | data class MenuMini( 4 | val id: Long, 5 | val name: String, 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/RestaurantInfo.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | import com.eatssu.android.data.enums.Restaurant 4 | 5 | data class RestaurantInfo( 6 | val enum: Restaurant, 7 | val name: String, 8 | val location: String, 9 | val photoUrl: String, 10 | val time: String, 11 | val etc: String, 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/Review.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | data class Review( 4 | val isWriter: Boolean, 5 | val reviewId: Long, 6 | 7 | val menu: String, 8 | val writerNickname: String, 9 | 10 | val mainGrade: Int, 11 | val amountGrade: Int, 12 | val tasteGrade: Int, 13 | 14 | val writeDate: String, 15 | 16 | val content: String, 17 | val imgUrl: ArrayList?, 18 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/ReviewInfo.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | data class ReviewInfo( 4 | var name: String, 5 | var reviewCnt: Int, 6 | var mainRating: Double, 7 | var amountRating: Double, 8 | var tasteRating: Double, 9 | var one: Int, 10 | var two: Int, 11 | var three: Int, 12 | var four: Int, 13 | var five: Int, 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/Section.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | import com.eatssu.android.data.enums.MenuType 4 | import com.eatssu.android.data.enums.Restaurant 5 | 6 | data class Section( 7 | val menuType: MenuType, 8 | val cafeteria: Restaurant, 9 | val menuList: List?, 10 | val cafeteriaLocation: String 11 | // val sortOrder: Int 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/model/TokenState.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.model 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.flow.StateFlow 5 | import timber.log.Timber 6 | 7 | enum class TokenState { 8 | INITIAL, VALID, EXPIRED, ERROR 9 | } 10 | 11 | /** 현재 토큰 상태를 관리하는 객체 */ 12 | object TokenStateManager { 13 | private val _state = MutableStateFlow(TokenState.INITIAL) 14 | val state: StateFlow = _state 15 | 16 | fun setTokenExpired() { 17 | _state.value = TokenState.EXPIRED 18 | Timber.e("TokenStateManager → Token expired") 19 | } 20 | 21 | fun setTokenValid() { 22 | _state.value = TokenState.VALID 23 | Timber.d("TokenStateManager → Token valid") 24 | } 25 | 26 | fun setTokenError() { 27 | _state.value = TokenState.ERROR 28 | Timber.e("TokenStateManager → Token error") 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/repository/MealRepository.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.repository 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.MenuOfMealResponse 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface MealRepository { 8 | // suspend fun getTodayMeal( 9 | // date: String, 10 | // restaurant: String, 11 | // time: String, 12 | // ): Flow>> 13 | 14 | suspend fun getMenuInfoByMealId( 15 | mealId: Long, 16 | ): Flow> 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/repository/OauthRepository.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.repository 2 | 3 | import com.eatssu.android.data.dto.request.CheckValidTokenRequest 4 | import com.eatssu.android.data.dto.request.LoginWithKakaoRequest 5 | import com.eatssu.android.data.dto.response.BaseResponse 6 | import com.eatssu.android.data.dto.response.TokenResponse 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | interface OauthRepository { 10 | suspend fun reissueToken( 11 | refreshToken: String, 12 | ): Flow> 13 | 14 | suspend fun login(body: LoginWithKakaoRequest): Flow> 15 | 16 | suspend fun checkValidToken(body: CheckValidTokenRequest): Flow> 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/repository/ReportRepository.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.repository 2 | 3 | import com.eatssu.android.data.dto.request.ReportRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface ReportRepository { 8 | suspend fun reportReview( 9 | body: ReportRequest, 10 | ): Flow> 11 | 12 | } 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/repository/ReviewRepository.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.repository 2 | 3 | import com.eatssu.android.data.dto.request.ModifyReviewRequest 4 | import com.eatssu.android.data.dto.request.WriteReviewRequest 5 | import com.eatssu.android.data.dto.response.BaseResponse 6 | import com.eatssu.android.data.dto.response.GetMealReviewInfoResponse 7 | import com.eatssu.android.data.dto.response.GetMenuReviewInfoResponse 8 | import com.eatssu.android.data.dto.response.GetReviewListResponse 9 | import com.eatssu.android.data.dto.response.ImageResponse 10 | import kotlinx.coroutines.flow.Flow 11 | import java.io.File 12 | 13 | interface ReviewRepository { 14 | 15 | suspend fun writeReview( 16 | menuId: Long, 17 | body: WriteReviewRequest, 18 | ): Flow> 19 | 20 | suspend fun deleteReview( 21 | reviewId: Long, 22 | ): Flow> 23 | 24 | suspend fun modifyReview( 25 | reviewId: Long, 26 | body: ModifyReviewRequest, 27 | ): Flow> 28 | 29 | suspend fun getReviewList( 30 | menuType: String, 31 | mealId: Long?, 32 | menuId: Long?, 33 | ): Flow> 34 | 35 | suspend fun getMenuReviewInfo( 36 | menuId: Long, 37 | ): Flow> 38 | 39 | 40 | suspend fun getMealReviewInfo( 41 | mealId: Long, 42 | ): Flow> 43 | 44 | suspend fun getImageString( 45 | file: File 46 | ): Flow> 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.repository 2 | 3 | import com.eatssu.android.data.dto.request.ChangeNicknameRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.data.dto.response.MyInfoResponse 6 | import com.eatssu.android.data.dto.response.MyReviewResponse 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | interface UserRepository { 10 | 11 | 12 | suspend fun updateUserName( 13 | body: ChangeNicknameRequest, 14 | ): Flow> 15 | 16 | suspend fun checkUserNameValidation( 17 | nickname: String, 18 | ): Flow> 19 | 20 | suspend fun getUserReviews(): Flow> 21 | suspend fun getUserInfo(): Flow> 22 | suspend fun signOut(): Flow> 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/alarm/AlarmUsecase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.alarm 2 | 3 | import android.app.AlarmManager 4 | import android.app.PendingIntent 5 | import android.content.Context 6 | import android.content.Intent 7 | import com.eatssu.android.alarm.NotificationReceiver 8 | import java.util.Calendar 9 | import javax.inject.Inject 10 | 11 | class AlarmUseCase @Inject constructor(private val context: Context) { 12 | 13 | fun scheduleAlarm() { 14 | val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager 15 | val intent = Intent(context, NotificationReceiver::class.java) 16 | val pendingIntent = PendingIntent.getBroadcast( 17 | context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT 18 | ) 19 | 20 | val calendar = Calendar.getInstance().apply { 21 | set(Calendar.HOUR_OF_DAY, 11) 22 | set(Calendar.MINUTE, 0) 23 | set(Calendar.SECOND, 0) 24 | set(Calendar.MILLISECOND, 0) 25 | } 26 | 27 | if (calendar.timeInMillis <= System.currentTimeMillis()) { 28 | calendar.add(Calendar.DAY_OF_YEAR, 1) 29 | } 30 | 31 | alarmManager.setRepeating( 32 | AlarmManager.RTC_WAKEUP, calendar.timeInMillis, AlarmManager.INTERVAL_DAY, pendingIntent 33 | ) 34 | } 35 | 36 | fun cancelAlarm() { 37 | val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager 38 | val intent = Intent(context, NotificationReceiver::class.java) 39 | val pendingIntent = PendingIntent.getBroadcast( 40 | context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT 41 | ) 42 | alarmManager.cancel(pendingIntent) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/alarm/GetDailyNotificationStatusUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.alarm 2 | 3 | import com.eatssu.android.data.repository.PreferencesRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import javax.inject.Inject 6 | 7 | class GetDailyNotificationStatusUseCase @Inject constructor( 8 | private val preferencesRepository: PreferencesRepository 9 | ) { 10 | operator fun invoke(): Flow { 11 | return preferencesRepository.dailyNotificationStatus 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/alarm/SetDailyNotificationStatusUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.alarm 2 | 3 | import com.eatssu.android.data.repository.PreferencesRepository 4 | import javax.inject.Inject 5 | 6 | 7 | class SetDailyNotificationStatusUseCase @Inject constructor( 8 | private val preferencesRepository: PreferencesRepository 9 | ) { 10 | suspend operator fun invoke(status: Boolean) { 11 | preferencesRepository.setDailyNotificationStatus(status) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetAccessTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class GetAccessTokenUseCase @Inject constructor( 9 | @ApplicationContext private val context: Context 10 | ) { 11 | suspend operator fun invoke(): String { 12 | return MySharedPreferences.getAccessToken(context) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetIsAccessTokenValidUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.request.CheckValidTokenRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.domain.repository.OauthRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetIsAccessTokenValidUseCase @Inject constructor( 10 | private val oauthRepository: OauthRepository 11 | ) { 12 | suspend operator fun invoke(userAccessToken: String): Flow> = 13 | oauthRepository.checkValidToken(CheckValidTokenRequest(userAccessToken)) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetMyReviewsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.MyReviewResponse 5 | import com.eatssu.android.domain.repository.UserRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetMyReviewsUseCase @Inject constructor( 10 | private val userRepository: UserRepository, 11 | ) { 12 | suspend operator fun invoke(): Flow> = 13 | userRepository.getUserReviews() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetRefreshTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class GetRefreshTokenUseCase @Inject constructor( 9 | @ApplicationContext private val context: Context 10 | // private val preferencesRepository: PreferencesRepository, 11 | ) { 12 | suspend operator fun invoke(): String { 13 | return MySharedPreferences.getRefreshToken(context) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetUserEmailUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class GetUserEmailUseCase @Inject constructor( 9 | @ApplicationContext private val context: Context 10 | ) { 11 | suspend operator fun invoke(): String { 12 | 13 | return MySharedPreferences.getUserEmail(context) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetUserInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.MyInfoResponse 5 | import com.eatssu.android.domain.repository.UserRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetUserInfoUseCase @Inject constructor( 10 | private val userRepository: UserRepository, 11 | ) { 12 | suspend operator fun invoke(): Flow> = 13 | userRepository.getUserInfo() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/GetUserNameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class GetUserNameUseCase @Inject constructor( 9 | // private val preferencesRepository: PreferencesRepository, 10 | @ApplicationContext private val context: Context 11 | ) { 12 | suspend operator fun invoke(): String { 13 | return MySharedPreferences.getUserName(context) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/LoginUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.request.LoginWithKakaoRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.data.dto.response.TokenResponse 6 | import com.eatssu.android.domain.repository.OauthRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class LoginUseCase @Inject constructor( 11 | private val oauthRepository: OauthRepository, 12 | ) { 13 | suspend operator fun invoke(body: LoginWithKakaoRequest): Flow> = 14 | oauthRepository.login(body) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/LogoutUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class LogoutUseCase @Inject constructor( 9 | @ApplicationContext private val context: Context 10 | ) { 11 | suspend operator fun invoke() { 12 | MySharedPreferences.clearUser(context) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/ReissueTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.TokenResponse 5 | import com.eatssu.android.domain.repository.OauthRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class ReissueTokenUseCase @Inject constructor( 10 | private val oauthRepository: OauthRepository, 11 | ) { 12 | suspend operator fun invoke(refreshToken: String): Flow> = 13 | oauthRepository.reissueToken(refreshToken) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/SetAccessTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class SetAccessTokenUseCase @Inject constructor( 9 | // private val preferencesRepository: PreferencesRepository, 10 | @ApplicationContext private val context: Context 11 | ) { 12 | suspend operator fun invoke(accessToken: String) { 13 | MySharedPreferences.setAccessToken(context, accessToken) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/SetRefreshTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class SetRefreshTokenUseCase @Inject constructor( 9 | // private val preferencesRepository: PreferencesRepository, 10 | @ApplicationContext private val context: Context 11 | ) { 12 | suspend operator fun invoke(refreshToken: String) { 13 | MySharedPreferences.setRefreshToken(context, refreshToken) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/SetUserEmailUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | 8 | class SetUserEmailUseCase @Inject constructor( 9 | @ApplicationContext private val context: Context 10 | ) { 11 | suspend operator fun invoke(email: String) { 12 | MySharedPreferences.setUserEmail(context, email) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/SetUserNameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import android.content.Context 4 | import com.eatssu.android.data.MySharedPreferences 5 | import com.eatssu.android.data.dto.request.ChangeNicknameRequest 6 | import com.eatssu.android.data.dto.response.BaseResponse 7 | import com.eatssu.android.domain.repository.UserRepository 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import kotlinx.coroutines.flow.Flow 10 | import javax.inject.Inject 11 | 12 | class SetUserNameUseCase @Inject constructor( 13 | private val userRepository: UserRepository, 14 | @ApplicationContext private val context: Context 15 | ) { 16 | suspend operator fun invoke(name: String): Flow> { 17 | MySharedPreferences.setUserName(context, name) 18 | //Todo 이게 최선일까? 로컬에 이름 Set과 리모트의 이름 change를 usecase를 따로 만들어야하나? 19 | 20 | return userRepository.updateUserName(ChangeNicknameRequest(name)) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/SignOutUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.domain.repository.UserRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class SignOutUseCase @Inject constructor( 9 | private val userRepository: UserRepository, 10 | ) { 11 | suspend operator fun invoke(): Flow> = 12 | userRepository.signOut() 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/auth/ValidateUserNameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.auth 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.domain.repository.UserRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class ValidateUserNameUseCase @Inject constructor( 9 | private val userRepository: UserRepository, 10 | ) { 11 | suspend operator fun invoke(name: String): Flow> = 12 | userRepository.checkUserNameValidation(name) 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/menu/GetMenuNameListOfMealUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.menu 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.MenuOfMealResponse 5 | import com.eatssu.android.domain.repository.MealRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetMenuNameListOfMealUseCase @Inject constructor( 10 | private val mealRepository: MealRepository, 11 | ) { 12 | suspend operator fun invoke(menuId: Long): Flow> = 13 | mealRepository.getMenuInfoByMealId(menuId) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/DeleteReviewUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.domain.repository.ReviewRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class DeleteReviewUseCase @Inject constructor( 9 | private val reviewRepository: ReviewRepository, 10 | ) { 11 | suspend operator fun invoke(reviewId: Long): Flow> = 12 | reviewRepository.deleteReview(reviewId) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/GetImageUrlUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.ImageResponse 5 | import com.eatssu.android.domain.repository.ReviewRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import java.io.File 8 | import javax.inject.Inject 9 | 10 | class GetImageUrlUseCase @Inject constructor( 11 | private val reviewRepository: ReviewRepository, 12 | ) { 13 | suspend operator fun invoke( 14 | file: File 15 | ): Flow> = 16 | reviewRepository.getImageString(file) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/GetMealReviewInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.GetMealReviewInfoResponse 5 | import com.eatssu.android.domain.repository.ReviewRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetMealReviewInfoUseCase @Inject constructor( 10 | private val reviewRepository: ReviewRepository, 11 | ) { 12 | suspend operator fun invoke(mealId: Long): Flow> = 13 | reviewRepository.getMealReviewInfo(mealId) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/GetMealReviewListUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.GetReviewListResponse 5 | import com.eatssu.android.data.enums.MenuType 6 | import com.eatssu.android.domain.repository.ReviewRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class GetMealReviewListUseCase @Inject constructor( 11 | private val reviewRepository: ReviewRepository, 12 | ) { 13 | suspend operator fun invoke( 14 | mealId: Long?, 15 | ): Flow> = 16 | reviewRepository.getReviewList(MenuType.VARIABLE.toString(), mealId, 0) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/GetMenuReviewInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.GetMenuReviewInfoResponse 5 | import com.eatssu.android.domain.repository.ReviewRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetMenuReviewInfoUseCase @Inject constructor( 10 | private val reviewRepository: ReviewRepository, 11 | ) { 12 | suspend operator fun invoke(menuId: Long): Flow> = 13 | reviewRepository.getMenuReviewInfo(menuId) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/GetMenuReviewListUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.response.BaseResponse 4 | import com.eatssu.android.data.dto.response.GetReviewListResponse 5 | import com.eatssu.android.data.enums.MenuType 6 | import com.eatssu.android.domain.repository.ReviewRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class GetMenuReviewListUseCase @Inject constructor( 11 | private val reviewRepository: ReviewRepository, 12 | ) { 13 | suspend operator fun invoke( 14 | menuId: Long?, 15 | ): Flow> = 16 | reviewRepository.getReviewList(MenuType.FIXED.toString(), 0, menuId) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/ModifyReviewUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.request.ModifyReviewRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.domain.repository.ReviewRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class ModifyReviewUseCase @Inject constructor( 10 | private val reviewRepository: ReviewRepository, 11 | ) { 12 | suspend operator fun invoke( 13 | reviewId: Long, 14 | body: ModifyReviewRequest, 15 | ): Flow> = 16 | reviewRepository.modifyReview(reviewId, body) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/PostReportUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.request.ReportRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.domain.repository.ReportRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class PostReportUseCase @Inject constructor( 10 | private val reportRepository: ReportRepository, 11 | ) { 12 | suspend operator fun invoke(body: ReportRequest): Flow> = 13 | reportRepository.reportReview(body) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/domain/usecase/review/WriteReviewUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.domain.usecase.review 2 | 3 | import com.eatssu.android.data.dto.request.WriteReviewRequest 4 | import com.eatssu.android.data.dto.response.BaseResponse 5 | import com.eatssu.android.domain.repository.ReviewRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class WriteReviewUseCase @Inject constructor( 10 | private val reviewRepository: ReviewRepository, 11 | ) { 12 | suspend operator fun invoke(menuId: Long, body: WriteReviewRequest): Flow> = 13 | reviewRepository.writeReview(menuId, body) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/UiEvent.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation 2 | 3 | 4 | /** 5 | * 각 Screen에 공통적인 이벤트 타입입니다. 6 | * 이벤트 타입을 추가하고 싶다면 UiEvent를 상속받아 사용하세요. 7 | */ 8 | interface UiEvent { 9 | data class ShowToast(val message: String) : UiEvent 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/UiState.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation 2 | 3 | sealed interface UiState { 4 | object Init : UiState 5 | 6 | object Loading : UiState 7 | 8 | data class Success( 9 | val data: T? = null, 10 | ) : UiState 11 | 12 | object Error : UiState 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewbinding.ViewBinding 9 | 10 | abstract class BaseFragment : Fragment() { 11 | 12 | private var _binding: B? = null 13 | val binding get() = _binding!! 14 | 15 | override fun onCreateView( 16 | inflater: LayoutInflater, 17 | container: ViewGroup?, 18 | savedInstanceState: Bundle?, 19 | ): View { 20 | _binding = setBinding(inflater) 21 | return binding.root 22 | } 23 | 24 | abstract fun setBinding(layoutInflater: LayoutInflater): B 25 | 26 | override fun onDestroyView() { 27 | super.onDestroyView() 28 | _binding = null 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/base/TokenEventBus.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.base 2 | 3 | import kotlinx.coroutines.flow.MutableSharedFlow 4 | import timber.log.Timber 5 | 6 | /** application에서 발생하는 토큰 만료 이벤트를 전달하기 위한 Bus */ 7 | object TokenEventBus { 8 | private val _tokenExpired = MutableSharedFlow(replay = 0) 9 | val tokenExpired = _tokenExpired 10 | 11 | private val _tokenServerError = MutableSharedFlow(replay = 0) 12 | val tokenServerError = _tokenServerError 13 | 14 | suspend fun notifyTokenExpired() { 15 | _tokenExpired.emit(Unit) 16 | } 17 | 18 | suspend fun notifyServerError() { 19 | Timber.d("TokenEventBus → Suver error") 20 | _tokenServerError.emit(Unit) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/CafeteriaViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import androidx.fragment.app.Fragment 6 | import androidx.fragment.app.FragmentActivity 7 | import androidx.viewpager2.adapter.FragmentStateAdapter 8 | import com.eatssu.android.data.enums.Time 9 | import com.eatssu.android.presentation.cafeteria.menu.MenuFragment 10 | import java.time.LocalTime 11 | 12 | class CafeteriaViewPagerAdapter(fragmentActivity: FragmentActivity) : 13 | FragmentStateAdapter(fragmentActivity) { 14 | 15 | // 1. ViewPager2에 연결할 Fragment 들을 생성 16 | private val fragmentList = listOf( 17 | MenuFragment.newInstance(Time.MORNING), 18 | MenuFragment.newInstance(Time.LUNCH), 19 | MenuFragment.newInstance(Time.DINNER) 20 | ) 21 | 22 | lateinit var menuDate : String 23 | 24 | // 2. ViesPager2에서 노출시킬 Fragment 의 갯수 설정 25 | override fun getItemCount(): Int { 26 | return fragmentList.size 27 | } 28 | 29 | // 3. ViewPager2의 각 페이지에서 노출할 Fragment 설정 30 | override fun createFragment(position: Int): Fragment { 31 | /*if(fragmentList[position] is LunchFragment) { 32 | (fragmentList[position] as LunchFragment).setDate(menuDate) 33 | }*/ 34 | return fragmentList[position] 35 | } 36 | 37 | // 4. 디폴트로 노출할 Fragment의 위치를 설정 38 | @RequiresApi(Build.VERSION_CODES.O) 39 | fun getDefaultFragmentPosition(): Int { 40 | // 여기에서 디폴트로 노출할 Fragment의 위치를 반환해줍니다. 41 | // 예를 들어, 첫 번째 Fragment를 디폴트로 설정하려면 0을 반환합니다. 42 | 43 | val time = LocalTime.now() 44 | 45 | val selectedIndex: Int = when (time.hour) { 46 | in 0..9 -> 0 //아침 47 | in 10..15 -> 1 //점심 48 | in 16..24 -> 2 //저녁 49 | else -> 1 50 | } 51 | return selectedIndex 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/calendar/CalendarViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.calendar 2 | 3 | import android.view.View 4 | import android.widget.TextView 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.eatssu.android.databinding.ItemCalendarListBinding 7 | import java.time.LocalDate 8 | 9 | class CalendarViewHolder internal constructor( 10 | binding: ItemCalendarListBinding, 11 | itemView: View, 12 | onItemListener: CalendarAdapter.OnItemListener, 13 | days: ArrayList 14 | ) : 15 | RecyclerView.ViewHolder(binding.root), View.OnClickListener { 16 | private val days: ArrayList 17 | val parentView: View 18 | val dayText: TextView 19 | val dayOfMonth: TextView 20 | private val onItemListener: CalendarAdapter.OnItemListener 21 | 22 | init { 23 | parentView = binding.weekCardview 24 | dayText = binding.day 25 | dayOfMonth = binding.date 26 | this.onItemListener = onItemListener 27 | binding.root.setOnClickListener(this) 28 | this.days = days 29 | } 30 | 31 | override fun onClick(view: View) { 32 | onItemListener.onItemClick(adapterPosition, days[adapterPosition]) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/info/InfoBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.info 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.activityViewModels 8 | import com.bumptech.glide.Glide 9 | import com.eatssu.android.data.enums.Restaurant 10 | import com.eatssu.android.databinding.FragmentBottomsheetInfoBinding 11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 12 | import kotlinx.coroutines.CoroutineScope 13 | import kotlinx.coroutines.Dispatchers 14 | import kotlinx.coroutines.launch 15 | import timber.log.Timber 16 | 17 | class InfoBottomSheetFragment : BottomSheetDialogFragment() { 18 | private var _binding: FragmentBottomsheetInfoBinding? = null 19 | private val binding get() = _binding!! 20 | 21 | private val infoViewModel: InfoViewModel by activityViewModels() 22 | 23 | override fun onCreateView( 24 | inflater: LayoutInflater, 25 | container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View { 28 | _binding = FragmentBottomsheetInfoBinding.inflate(inflater, container, false) 29 | return binding.root 30 | } 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | 35 | val name = arguments?.getString("name") 36 | val restaurantType = enumValues().find { it.name == name } ?: Restaurant.HAKSIK 37 | Timber.d("onViewCreated: $name $restaurantType") 38 | 39 | binding.tvName.text = restaurantType.displayName 40 | 41 | CoroutineScope(Dispatchers.Main).launch { 42 | infoViewModel.infoList.collect { 43 | val restaurantInfo = infoViewModel.getRestaurantInfo(restaurantType) 44 | 45 | restaurantInfo?.let { 46 | binding.tvLocation.text = it.location 47 | binding.tvTime.text = it.time 48 | binding.tvEtc.text = it.etc 49 | 50 | Glide.with(this@InfoBottomSheetFragment) 51 | .load(it.photoUrl) 52 | .into(binding.ivCafeteriaPhoto) 53 | } 54 | } 55 | } 56 | } 57 | 58 | companion object { 59 | fun newInstance(data: String): InfoBottomSheetFragment { 60 | val fragment = InfoBottomSheetFragment() 61 | val args = Bundle().apply { putString("name", data) } 62 | fragment.arguments = args 63 | return fragment 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/info/InfoViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.info 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.eatssu.android.data.enums.Restaurant 5 | import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository 6 | import com.eatssu.android.domain.model.RestaurantInfo 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.flow.asStateFlow 11 | import timber.log.Timber 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class InfoViewModel @Inject constructor( 16 | private val firebaseRemoteConfigRepository: FirebaseRemoteConfigRepository 17 | ) : ViewModel() { 18 | 19 | private val _infoList = MutableStateFlow>(emptyList()) 20 | val infoList: StateFlow> = _infoList.asStateFlow() 21 | 22 | private val restaurantInfoMap: MutableMap = mutableMapOf() 23 | 24 | init { 25 | _infoList.value = firebaseRemoteConfigRepository.getCafeteriaInfo() 26 | Timber.d(_infoList.value.toString()) 27 | _infoList.value.forEach { restaurantInfo -> 28 | restaurantInfoMap[restaurantInfo.enum] = restaurantInfo 29 | } 30 | } 31 | 32 | fun getRestaurantInfo(restaurant: Restaurant): RestaurantInfo? { 33 | return restaurantInfoMap[restaurant] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/menu/MenuAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.menu 2 | 3 | import android.util.Log 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.fragment.app.DialogFragment 7 | import androidx.fragment.app.FragmentManager 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.eatssu.android.R 11 | import com.eatssu.android.databinding.ItemCafeteriaSectionBinding 12 | import com.eatssu.android.domain.model.Section 13 | import com.eatssu.android.presentation.cafeteria.info.InfoBottomSheetFragment 14 | 15 | class MenuAdapter( 16 | private val fragmentManager: FragmentManager, 17 | private val totalMenuList: ArrayList
18 | 19 | ) : RecyclerView.Adapter() { 20 | 21 | class MyViewHolder( 22 | private val binding: ItemCafeteriaSectionBinding 23 | ) : RecyclerView.ViewHolder(binding.root) { 24 | 25 | fun bind( 26 | fragmentManager: FragmentManager, 27 | sectionModel: Section 28 | ) { 29 | 30 | binding.llCafeteriaInfo.setOnClickListener { 31 | 32 | val modalBottomSheet = 33 | InfoBottomSheetFragment.newInstance(sectionModel.cafeteria.name) 34 | modalBottomSheet.setStyle( 35 | DialogFragment.STYLE_NORMAL, 36 | R.style.RoundCornerBottomSheetDialogTheme 37 | ) 38 | modalBottomSheet.show(fragmentManager, "Open Bottom Sheet") 39 | Log.d("MenuAdapter", "bind: ${sectionModel.cafeteria}") 40 | } 41 | 42 | binding.tvCafeteria.text = sectionModel.cafeteria.displayName 43 | binding.tvCafeteriaLocation.text = sectionModel.cafeteriaLocation 44 | 45 | binding.rvMenu.apply { 46 | setHasFixedSize(true) 47 | layoutManager = LinearLayoutManager(binding.root.context) 48 | adapter = sectionModel.menuList?.let { 49 | MenuSubAdapter(it, sectionModel.cafeteria.menuType) 50 | } 51 | } 52 | } 53 | } 54 | 55 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 56 | return MyViewHolder( 57 | ItemCafeteriaSectionBinding.inflate( 58 | LayoutInflater.from(parent.context), 59 | parent, false)) 60 | } 61 | 62 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 63 | totalMenuList[position].let { sectionModel -> 64 | holder.bind(fragmentManager, sectionModel) 65 | } 66 | } 67 | 68 | override fun getItemCount(): Int = totalMenuList.size 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/review/modify/ModifyViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.review.modify 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.eatssu.android.App 6 | import com.eatssu.android.R 7 | import com.eatssu.android.data.dto.request.ModifyReviewRequest 8 | import com.eatssu.android.domain.usecase.review.ModifyReviewUseCase 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.flow.asStateFlow 13 | import kotlinx.coroutines.flow.catch 14 | import kotlinx.coroutines.flow.collectLatest 15 | import kotlinx.coroutines.flow.onCompletion 16 | import kotlinx.coroutines.flow.onStart 17 | import kotlinx.coroutines.flow.update 18 | import kotlinx.coroutines.launch 19 | import timber.log.Timber 20 | import javax.inject.Inject 21 | 22 | @HiltViewModel 23 | class ModifyViewModel @Inject constructor( 24 | private val modifyReviewUseCase: ModifyReviewUseCase, 25 | ) : ViewModel() { 26 | 27 | private val _uiState: MutableStateFlow = MutableStateFlow(ModifyState()) 28 | val uiState: StateFlow = _uiState.asStateFlow() 29 | 30 | fun modifyMyReview( 31 | reviewId: Long, 32 | body: ModifyReviewRequest, 33 | ) { 34 | viewModelScope.launch { 35 | modifyReviewUseCase(reviewId, body).onStart { 36 | _uiState.update { it.copy(loading = true) } 37 | }.onCompletion { 38 | _uiState.update { it.copy(loading = false, error = true) } 39 | }.catch { e -> 40 | _uiState.update { 41 | it.copy( 42 | loading = false, 43 | error = false, 44 | isDone = true, 45 | toastMessage = App.appContext.getString(R.string.modify_not) 46 | ) 47 | } 48 | Timber.e(e.toString()) 49 | }.collectLatest { result -> 50 | Timber.d(result.toString()) 51 | _uiState.update { 52 | it.copy( 53 | loading = false, 54 | error = false, 55 | isDone = true, 56 | toastMessage = App.appContext.getString(R.string.modify_done) 57 | ) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | data class ModifyState( 65 | var loading: Boolean = true, 66 | var error: Boolean = false, 67 | var toastMessage: String = "", 68 | 69 | var isDone: Boolean = false, 70 | 71 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/review/report/ReportViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.review.report 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.eatssu.android.data.dto.request.ReportRequest 7 | import com.eatssu.android.domain.usecase.review.PostReportUseCase 8 | import com.eatssu.android.presentation.mypage.usernamechange.UserNameChangeViewModel.Companion.TAG 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.flow.asStateFlow 13 | import kotlinx.coroutines.flow.catch 14 | import kotlinx.coroutines.flow.collectLatest 15 | import kotlinx.coroutines.flow.onCompletion 16 | import kotlinx.coroutines.flow.onStart 17 | import kotlinx.coroutines.flow.update 18 | import kotlinx.coroutines.launch 19 | import javax.inject.Inject 20 | 21 | @HiltViewModel 22 | class ReportViewModel 23 | @Inject constructor( 24 | private val postReportUseCase: PostReportUseCase, 25 | ) : ViewModel() { 26 | 27 | private val _uiState: MutableStateFlow = 28 | MutableStateFlow(ReportUiState()) 29 | val uiState: StateFlow = _uiState.asStateFlow() 30 | 31 | 32 | fun postData(reviewId: Long, reportType: String, content: String) { 33 | viewModelScope.launch { 34 | postReportUseCase(ReportRequest(reviewId, reportType, content)).onStart { 35 | _uiState.update { it.copy(loading = true) } 36 | }.onCompletion { 37 | _uiState.update { it.copy(loading = false, error = true) } 38 | }.catch { e -> 39 | _uiState.update { it.copy(error = true, toastMessage = "신고가 실패하였습니다.") } 40 | Log.e(TAG, e.toString()) 41 | }.collectLatest { result -> 42 | _uiState.update { it.copy(isDone = true, toastMessage = "신고가 완료되었습니다.") } 43 | } 44 | } 45 | } 46 | } 47 | 48 | data class ReportUiState( 49 | var loading: Boolean = true, 50 | var error: Boolean = false, 51 | 52 | var toastMessage: String = "", 53 | var isDone: Boolean = false, 54 | ) 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/ReviewWriteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.review.write 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.eatssu.android.data.dto.request.WriteReviewRequest 6 | import com.eatssu.android.domain.usecase.review.GetImageUrlUseCase 7 | import com.eatssu.android.domain.usecase.review.WriteReviewUseCase 8 | import com.eatssu.android.presentation.UiEvent 9 | import com.eatssu.android.presentation.UiState 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.flow.MutableSharedFlow 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.flow.asSharedFlow 14 | import kotlinx.coroutines.flow.asStateFlow 15 | import kotlinx.coroutines.flow.catch 16 | import kotlinx.coroutines.flow.collectLatest 17 | import kotlinx.coroutines.flow.firstOrNull 18 | import kotlinx.coroutines.flow.map 19 | import kotlinx.coroutines.flow.onStart 20 | import kotlinx.coroutines.launch 21 | import timber.log.Timber 22 | import java.io.File 23 | import javax.inject.Inject 24 | 25 | 26 | @HiltViewModel 27 | class UploadReviewViewModel @Inject constructor( 28 | private val writeReviewUseCase: WriteReviewUseCase, 29 | private val getImageUrlUseCase: GetImageUrlUseCase, 30 | ) : ViewModel() { 31 | 32 | private val _uiState = MutableStateFlow>(UiState.Init) 33 | val uiState = _uiState.asStateFlow() 34 | 35 | private val _uiEvent: MutableSharedFlow = MutableSharedFlow() 36 | val uiEvent = _uiEvent.asSharedFlow() 37 | 38 | fun postReview(menuId: Long, reviewData: WriteReviewRequest) { 39 | viewModelScope.launch { 40 | writeReviewUseCase(menuId, reviewData) 41 | .onStart { 42 | _uiState.value = UiState.Loading 43 | } 44 | .catch { e -> 45 | _uiState.value = UiState.Error 46 | _uiEvent.emit(UiEvent.ShowToast("리뷰 작성에 실패하였습니다.")) 47 | Timber.e(e) 48 | } 49 | .collectLatest { 50 | _uiState.value = UiState.Success() 51 | _uiEvent.emit(UiEvent.ShowToast("리뷰가 작성되었습니다.")) 52 | } 53 | } 54 | } 55 | 56 | suspend fun saveS3(file: File): String? { 57 | return getImageUrlUseCase(file) 58 | .onStart { 59 | _uiState.value = UiState.Loading 60 | } 61 | .catch { e -> 62 | _uiState.value = UiState.Error 63 | _uiEvent.emit(UiEvent.ShowToast("이미지 업로드에 실패하였습니다.")) 64 | Timber.e(e) 65 | } 66 | .map { it.result?.url } 67 | .firstOrNull() 68 | } 69 | } 70 | 71 | sealed class UploadReviewState 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/menu/VariableMenuPickAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.review.write.menu 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.eatssu.android.databinding.ItemMenuPickBinding 7 | import com.eatssu.android.domain.model.MenuMini 8 | 9 | class VariableMenuPickAdapter(private val menuList: List?) : 10 | RecyclerView.Adapter() { 11 | 12 | private val checkedItems: ArrayList> = ArrayList() 13 | 14 | inner class ViewHolder(private val binding: ItemMenuPickBinding) : 15 | RecyclerView.ViewHolder(binding.root) { 16 | 17 | fun bind(position: Int) { 18 | val menuItem = menuList?.get(position) 19 | with(binding) { 20 | tvMenuName.text = menuItem?.name 21 | checkBox.isChecked = checkedItems.contains(getItem(position)) 22 | checkBox.setOnClickListener { onCheckBoxClick(position) } 23 | } 24 | } 25 | } 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 28 | val binding = 29 | ItemMenuPickBinding.inflate(LayoutInflater.from(parent.context), parent, false) 30 | return ViewHolder(binding) 31 | } 32 | 33 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 34 | holder.bind(position) 35 | } 36 | 37 | override fun getItemCount(): Int = menuList?.size ?: 0 38 | 39 | private fun getItem(position: Int): Pair { 40 | val menuItem = menuList?.get(position) 41 | return Pair(menuItem?.name, menuItem?.id) 42 | } 43 | 44 | private fun onCheckBoxClick(position: Int) { 45 | val item = getItem(position) 46 | if (checkedItems.contains(item)) { 47 | checkedItems.remove(item) 48 | } else { 49 | checkedItems.add(item as Pair) 50 | } 51 | } 52 | 53 | fun sendCheckedItem(): ArrayList> = checkedItems 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/menu/VariableMenuViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.cafeteria.review.write.menu 2 | 3 | 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.eatssu.android.data.dto.response.toMenuMiniList 7 | import com.eatssu.android.domain.model.MenuMini 8 | import com.eatssu.android.domain.usecase.menu.GetMenuNameListOfMealUseCase 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.flow.asStateFlow 13 | import kotlinx.coroutines.flow.catch 14 | import kotlinx.coroutines.flow.collectLatest 15 | import kotlinx.coroutines.flow.onCompletion 16 | import kotlinx.coroutines.flow.onStart 17 | import kotlinx.coroutines.flow.update 18 | import kotlinx.coroutines.launch 19 | import timber.log.Timber 20 | import javax.inject.Inject 21 | 22 | 23 | @HiltViewModel 24 | class VariableMenuViewModel @Inject constructor( 25 | private val getMenuNameListUseCase: GetMenuNameListOfMealUseCase, 26 | ) : ViewModel() { 27 | 28 | private val _uiState: MutableStateFlow = MutableStateFlow(MenuState()) 29 | val uiState: StateFlow = _uiState.asStateFlow() 30 | 31 | fun findMenuItemByMealId(mealId: Long) { 32 | Timber.d("findMenuItemByMealId: $mealId") 33 | viewModelScope.launch { 34 | getMenuNameListUseCase( 35 | mealId 36 | ).onStart { 37 | Timber.d("findMenuItemByMealId: onStart") 38 | 39 | _uiState.update { it.copy(loading = true) } 40 | }.onCompletion { 41 | Timber.d("findMenuItemByMealId: onCompletion") 42 | 43 | _uiState.update { it.copy(loading = false, error = true) } 44 | }.catch { e -> 45 | Timber.d("findMenuItemByMealId: catch $e") 46 | 47 | _uiState.update { 48 | it.copy( 49 | loading = false, 50 | error = true, 51 | ) 52 | } 53 | }.collectLatest { result -> 54 | Timber.d("findMenuItemByMealId: ${result.toString()}") 55 | _uiState.update { 56 | it.copy( 57 | loading = false, 58 | error = false, 59 | menuOfMeal = result.result?.toMenuMiniList() 60 | ) 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | data class MenuState( 68 | var loading: Boolean = true, 69 | var error: Boolean = false, 70 | var menuOfMeal: List? = null, 71 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/common/AndroidMessageDialogActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.common 2 | 3 | 4 | import android.app.AlertDialog 5 | import android.os.Bundle 6 | import android.util.Log 7 | import androidx.appcompat.app.AppCompatActivity 8 | 9 | 10 | class AndroidMessageDialogActivity : AppCompatActivity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | showDialog() 15 | } 16 | 17 | private fun showDialog() { 18 | val builder = AlertDialog.Builder(this) 19 | 20 | builder.setTitle("공지") 21 | val message = intent.getStringExtra("message") 22 | Log.d("message",message.toString()) 23 | builder.setMessage(intent.getStringExtra("message")) 24 | 25 | builder.setPositiveButton("확인") { dialog, which -> 26 | // Google Play Store의 앱 페이지로 이동하여 업데이트를 다운로드합니다. 27 | 28 | // 다이얼로그를 종료합니다. 29 | finish() 30 | } 31 | 32 | builder.setCancelable(false) // 사용자가 다이얼로그를 취소할 수 없도록 설정 33 | 34 | val dialog = builder.create() 35 | dialog.show() 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/common/ForceUpdateActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.common 2 | 3 | 4 | import android.app.AlertDialog 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Bundle 8 | import androidx.appcompat.app.AppCompatActivity 9 | 10 | 11 | class ForceUpdateDialogActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | showForceUpdateDialog() 16 | } 17 | 18 | private fun showForceUpdateDialog() { 19 | val builder = AlertDialog.Builder(this) 20 | builder.setTitle("강제 업데이트") 21 | builder.setMessage("새 버전의 앱을 설치해야 합니다.") 22 | 23 | builder.setPositiveButton("업데이트") { dialog, which -> 24 | // Google Play Store의 앱 페이지로 이동하여 업데이트를 다운로드합니다. 25 | val appPackageName = packageName 26 | try { 27 | startActivity( 28 | Intent( 29 | Intent.ACTION_VIEW, 30 | Uri.parse("market://details?id=$appPackageName") 31 | ) 32 | ) 33 | } catch (e: android.content.ActivityNotFoundException) { 34 | startActivity( 35 | Intent( 36 | Intent.ACTION_VIEW, 37 | Uri.parse("https://play.google.com/store/apps/details?id=$appPackageName") 38 | ) 39 | ) 40 | } 41 | 42 | // 다이얼로그를 종료합니다. 43 | finish() 44 | } 45 | 46 | builder.setCancelable(false) // 사용자가 다이얼로그를 취소할 수 없도록 설정 47 | 48 | val dialog = builder.create() 49 | dialog.show() 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/common/NetworkConnection.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.common 2 | 3 | import android.app.AlertDialog 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.Network 7 | import android.net.NetworkCapabilities 8 | import android.net.NetworkRequest 9 | 10 | // 네트워크 연결 확인을 위해 네트워크 변경 시 알람에 사용하는 클래스 NetworkCallback 을 커스터마이징 11 | class NetworkConnection(private val context: Context) : 12 | ConnectivityManager.NetworkCallback() { 13 | 14 | private val connectivityManager: ConnectivityManager = 15 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 16 | private val networkRequest: NetworkRequest = NetworkRequest.Builder() 17 | .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) // 데이터 사용 관련 감지 18 | .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // 와이파이 사용 관련 감지 19 | .build() 20 | 21 | // 네트워크 연결 안 되어있을 때 보여줄 다이얼로그 22 | private val dialog: AlertDialog by lazy { 23 | AlertDialog.Builder(context) 24 | .setTitle("네트워크 연결 안 됨") 25 | .setMessage("와이파이 또는 모바일 데이터를 확인해주세요") 26 | .create() 27 | } 28 | 29 | // NetworkCallback 등록 30 | fun register() { 31 | connectivityManager.registerNetworkCallback(networkRequest, this) 32 | } 33 | 34 | // NetworkCallback 해제 35 | fun unregister() { 36 | connectivityManager.unregisterNetworkCallback(this) 37 | } 38 | 39 | // 현재 네트워크 상태 확인 40 | fun getConnectivityStatus(): Network? { 41 | // 연결된 네트워크가 없을 시 null 리턴 42 | return connectivityManager.activeNetwork 43 | } 44 | 45 | // 콜백이 등록되거나 네트워크가 연결되었을 때 실행되는 메소드 46 | override fun onAvailable(network: Network) { 47 | super.onAvailable(network) 48 | 49 | if (getConnectivityStatus() == null) { 50 | // 네트워크 연결 안 되어 있을 때 51 | dialog.show() 52 | } else { 53 | // 네트워크 연결 되어 있을 때 54 | dialog.dismiss() 55 | } 56 | } 57 | 58 | // 네트워크 끊겼을 때 실행되는 메소드 59 | override fun onLost(network: Network) { 60 | super.onLost(network) 61 | dialog.show() 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/common/OthersBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.common 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.eatssu.android.databinding.FragmentBottomsheetOthersBinding 9 | import com.eatssu.android.presentation.cafeteria.review.report.ReportActivity 10 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import timber.log.Timber 13 | 14 | @AndroidEntryPoint 15 | class OthersBottomSheetFragment : BottomSheetDialogFragment() { 16 | private var _binding: FragmentBottomsheetOthersBinding? = null 17 | private val binding get() = _binding!! 18 | 19 | var reviewId = -1L 20 | var menu = "" 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, 24 | container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View { 27 | _binding = FragmentBottomsheetOthersBinding.inflate(inflater, container, false) 28 | return binding.root 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | 34 | arguments?.let { 35 | reviewId = it.getLong("reviewId") 36 | menu = it.getString("menu").toString() 37 | } 38 | 39 | Timber.d("넘겨받은 리뷰 정보: $reviewId $menu") 40 | 41 | binding.llReport.setOnClickListener { 42 | 43 | val intent = Intent(requireContext(), ReportActivity::class.java) 44 | intent.putExtra("reviewId", reviewId) 45 | Timber.d("reviewId $reviewId") 46 | startActivity(intent) 47 | dismiss() 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/common/VersionViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.common 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.eatssu.android.BuildConfig.VERSION_CODE 5 | import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository 6 | import timber.log.Timber 7 | 8 | class VersionViewModel(private val repository: FirebaseRemoteConfigRepository) : ViewModel() { 9 | 10 | init { 11 | // Repository 초기화 12 | repository.init() 13 | } 14 | 15 | fun checkForceUpdate(): Boolean { 16 | 17 | val versionCode = checkVersionCode() //얘가 파이어베이스에 있는 최신 버전 18 | val thisCheckVersionCode = VERSION_CODE 19 | 20 | Timber.d("앱의 versionCode는 " + thisCheckVersionCode + " 배포된 최신 버전은 " + versionCode) 21 | 22 | if (thisCheckVersionCode < versionCode) { //배포된 버전이 크면 강제 업데이트 23 | Timber.d("강제업데이트") 24 | return true 25 | } else if (thisCheckVersionCode >= versionCode) { //이 버전이 더 크거나 같으면 강제 업데이트 할 필요 x 26 | Timber.d("업데이트 패스~") 27 | return false 28 | } 29 | return false 30 | } 31 | 32 | 33 | fun checkVersionCode(): Long { 34 | return repository.getVersionCode() 35 | } 36 | 37 | // fun checkAndroidMessage(): AndroidMessage { 38 | // return repository.getAndroidMessage() 39 | // } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/common/VersionViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.common 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository 6 | 7 | class VersionViewModelFactory(private val repository: FirebaseRemoteConfigRepository) : 8 | ViewModelProvider.Factory { 9 | 10 | override fun create(modelClass: Class): T { 11 | if (modelClass.isAssignableFrom(VersionViewModel::class.java)) { 12 | @Suppress("UNCHECKED_CAST") 13 | return VersionViewModel(repository) as T 14 | } 15 | throw IllegalArgumentException("Unknown ViewModel class") 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/compose/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.compose.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | // Gray Scale 6 | val White = Color(0xFFFFFFFF) 7 | val Black = Color(0xFF000000) 8 | val Gray100 = Color(0xFFFAFAFB) 9 | val Gray200 = Color(0xFFF0F0F0) 10 | val Gray300 = Color(0xFFDEDEDE) 11 | val Gray400 = Color(0xFFA8A8A8) 12 | val Gray500 = Color(0xFF9D9D9D) 13 | val Gray600 = Color(0xFF565656) 14 | val Gray700 = Color(0xFF1F1F1F) 15 | 16 | // Brand 17 | val Primary = Color(0xFF66D4C2) 18 | val Secondary = Color(0xFFEEFBF8) 19 | val Star = Color(0xFFFFC700) 20 | val Error = Color(0xFFFF3F3F) 21 | 22 | // Special 23 | val KakaoLogin = Color(0xFFFEE500) 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/login/IntroActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.login 2 | 3 | import android.os.Bundle 4 | import androidx.activity.viewModels 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.lifecycle.lifecycleScope 7 | import com.eatssu.android.databinding.ActivityIntroBinding 8 | import com.eatssu.android.presentation.UiEvent 9 | import com.eatssu.android.presentation.UiState 10 | import com.eatssu.android.presentation.MainActivity 11 | import com.eatssu.android.presentation.util.showToast 12 | import com.eatssu.android.presentation.util.startActivity 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import kotlinx.coroutines.flow.collectLatest 15 | import kotlinx.coroutines.launch 16 | 17 | @AndroidEntryPoint 18 | class IntroActivity : AppCompatActivity() { 19 | 20 | private val introViewModel: IntroViewModel by viewModels() 21 | private lateinit var binding: ActivityIntroBinding 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | binding = ActivityIntroBinding.inflate(layoutInflater) 26 | setContentView(binding.root) 27 | 28 | lifecycleScope.launch { 29 | introViewModel.uiState.collectLatest { state -> 30 | when (state) { 31 | is UiState.Success -> { 32 | startActivity() 33 | finish() 34 | } 35 | 36 | is UiState.Error -> { 37 | // 로그인 액티비티로 이동 38 | startActivity() 39 | finish() 40 | } 41 | 42 | else -> Unit 43 | } 44 | } 45 | 46 | introViewModel.uiEvent.collectLatest { event -> 47 | when (event) { 48 | is UiEvent.ShowToast -> { 49 | // 에러 메시지 표시 50 | showToast(event.message) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/login/IntroViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.login 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.eatssu.android.domain.usecase.auth.GetAccessTokenUseCase 6 | import com.eatssu.android.domain.usecase.auth.GetIsAccessTokenValidUseCase 7 | import com.eatssu.android.presentation.UiEvent 8 | import com.eatssu.android.presentation.UiState 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableSharedFlow 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.flow.SharedFlow 13 | import kotlinx.coroutines.flow.StateFlow 14 | import kotlinx.coroutines.flow.asStateFlow 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class IntroViewModel @Inject constructor( 20 | private val getAccessTokenUseCase: GetAccessTokenUseCase, 21 | private val getIsAccessTokenValidUseCase: GetIsAccessTokenValidUseCase 22 | ) : ViewModel() { 23 | 24 | private val _uiState: MutableStateFlow> = MutableStateFlow(UiState.Init) 25 | val uiState: StateFlow> = _uiState.asStateFlow() 26 | 27 | private val _uiEvent = MutableSharedFlow() 28 | val uiEvent: SharedFlow = _uiEvent 29 | 30 | init { 31 | autoLogin() 32 | } 33 | 34 | private fun autoLogin() { 35 | viewModelScope.launch { 36 | val userAccessToken = getAccessTokenUseCase() 37 | 38 | _uiState.value = UiState.Loading 39 | try { 40 | // 토큰 존재 여부 확인 41 | if (userAccessToken.isEmpty()) { 42 | _uiState.value = UiState.Error 43 | _uiEvent.emit(UiEvent.ShowToast("로그인이 필요합니다")) 44 | return@launch 45 | } else { 46 | checkValid(userAccessToken) 47 | } 48 | 49 | } catch (e: Exception) { 50 | _uiState.value = UiState.Error 51 | _uiEvent.emit(UiEvent.ShowToast("오류가 발생했습니다: ${e.message}")) 52 | } 53 | } 54 | } 55 | 56 | private fun checkValid(userAccessToken: String) { 57 | viewModelScope.launch { 58 | getIsAccessTokenValidUseCase(userAccessToken) 59 | .collect { 60 | if (it.result == true) { //토큰이 있고 유효함 61 | _uiState.value = UiState.Success(IntroState.ValidToken) 62 | } else { //토큰이 있어도 유효하지 않음 63 | _uiState.value = UiState.Error 64 | _uiEvent.emit(UiEvent.ShowToast("로그인이 필요합니다")) 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | sealed class IntroState { 72 | object ValidToken : IntroState() 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/login/UserApiClient.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.login 2 | 3 | import android.content.Context 4 | import com.kakao.sdk.auth.model.OAuthToken 5 | import com.kakao.sdk.common.model.ClientError 6 | import com.kakao.sdk.common.model.ClientErrorCause 7 | import com.kakao.sdk.user.UserApiClient 8 | import kotlin.coroutines.resume 9 | import kotlin.coroutines.resumeWithException 10 | import kotlin.coroutines.suspendCoroutine 11 | 12 | 13 | /** 14 | * 카카오톡 설치 여부에 따라서 설치 되어있으면 카카오톡 로그인을 시도한다. 15 | * 미설치 시 카카오 계정 로그인을 시도한다. 16 | * 17 | * 카카오톡 로그인에 실패하면 사용자가 의도적으로 로그인 취소한 경우를 제외하고는 카카오 계정 로그인을 서브로 실행한다. 18 | */ 19 | suspend fun UserApiClient.Companion.loginWithKakao(context: Context): OAuthToken { 20 | return if (instance.isKakaoTalkLoginAvailable(context)) { 21 | try { 22 | UserApiClient.loginWithKakaoTalk(context) 23 | } catch (error: Throwable) { 24 | // 사용자가 카카오톡 설치 후 디바이스 권한 요청 화면에서 로그인을 취소한 경우, 25 | // 의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리 (예: 뒤로 가기) 26 | // 그냥 에러를 올린다. 27 | if (error is ClientError && error.reason == ClientErrorCause.Cancelled) throw error 28 | 29 | // 그렇지 않다면, 카카오 계정 로그인을 시도한다. 30 | UserApiClient.loginWithKakaoAccount(context) 31 | } 32 | } else { 33 | UserApiClient.loginWithKakaoAccount(context) 34 | } 35 | } 36 | 37 | /** 38 | * 카카오톡으로 로그인 시도 39 | */ 40 | suspend fun UserApiClient.Companion.loginWithKakaoTalk(context: Context): OAuthToken { 41 | return suspendCoroutine { continuation -> 42 | instance.loginWithKakaoTalk(context) { token, error -> 43 | if (error != null) { 44 | continuation.resumeWithException(error) 45 | } else if (token != null) { 46 | continuation.resume(token) 47 | } else { 48 | continuation.resumeWithException(RuntimeException("kakao access token을 받아오는데 실패함, 이유는 명확하지 않음.")) 49 | } 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * 카카오 계정으로 로그인 시도 56 | */ 57 | suspend fun UserApiClient.Companion.loginWithKakaoAccount(context: Context): OAuthToken { 58 | return suspendCoroutine { continuation -> 59 | instance.loginWithKakaoAccount(context) { token, error -> 60 | if (error != null) { 61 | continuation.resumeWithException(error) 62 | } else if (token != null) { 63 | continuation.resume(token) 64 | } else { 65 | continuation.resumeWithException(RuntimeException("kakao access token을 받아오는데 실패함, 이유는 명확하지 않음.")) 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/map/MapFragment.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.map 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.compose.ui.platform.ComposeView 9 | import com.eatssu.android.presentation.compose.ui.theme.EatssuTheme 10 | 11 | class MapFragment : Fragment() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | 16 | } 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, container: ViewGroup?, 20 | savedInstanceState: Bundle? 21 | ): View { 22 | // Inflate the layout for this fragment 23 | return ComposeView(requireContext()).apply { 24 | setContent { 25 | EatssuTheme { 26 | MapFragmentComposeView() 27 | } 28 | } 29 | } 30 | // return inflater.inflate(R.layout.fragment_map, container, false) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/map/MapFragmentComposeView.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.map 2 | 3 | import androidx.compose.material3.Text 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.tooling.preview.Preview 6 | import com.eatssu.android.presentation.compose.ui.theme.EatssuTheme 7 | import com.eatssu.android.presentation.compose.ui.theme.Primary 8 | 9 | @Composable 10 | fun MapFragmentComposeView() { 11 | Text( 12 | text = "Hello, Eatssu!", 13 | style = EatssuTheme.typography.body1, 14 | color = Primary 15 | ) 16 | } 17 | 18 | @Preview(showBackground = true) 19 | @Composable 20 | fun MapFragmentComposeViewPreview() { 21 | EatssuTheme { 22 | MapFragmentComposeView() 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/mypage/DeveloperActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.mypage 2 | 3 | import android.os.Bundle 4 | import com.eatssu.android.R 5 | import com.eatssu.android.databinding.ActivityDeveloperBinding 6 | import com.eatssu.android.presentation.base.BaseActivity 7 | import dagger.hilt.android.AndroidEntryPoint 8 | 9 | @AndroidEntryPoint 10 | class DeveloperActivity : 11 | BaseActivity(ActivityDeveloperBinding::inflate) { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | 15 | toolbarTitle.text = getString(R.string.developer) // 툴바 제목 설정 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/mypage/SignOutActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.mypage 2 | 3 | import android.os.Bundle 4 | import android.text.Editable 5 | import android.text.TextWatcher 6 | import androidx.activity.viewModels 7 | import androidx.lifecycle.lifecycleScope 8 | import com.eatssu.android.databinding.ActivitySignOutBinding 9 | import com.eatssu.android.presentation.base.BaseActivity 10 | import com.eatssu.android.presentation.util.showToast 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import kotlinx.coroutines.flow.collectLatest 13 | import kotlinx.coroutines.launch 14 | 15 | @AndroidEntryPoint 16 | class SignOutActivity : 17 | BaseActivity(ActivitySignOutBinding::inflate) { 18 | //TODO 현재 dev서버 탈퇴하기 500 19 | 20 | private val signOutViewModel: SignOutViewModel by viewModels() 21 | 22 | private var inputNickname: String = "" 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | toolbarTitle.text = "탈퇴하기" // 툴바 제목 설정 27 | 28 | val nickname = intent.getStringExtra("nickname") 29 | 30 | binding.btnSignOut.isEnabled = false 31 | 32 | // binding.etEnterNickname.hint = 33 | binding.etEnterNickname.addTextChangedListener(object : TextWatcher { 34 | override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} 35 | 36 | //값 변경 시 실행되는 함수 37 | override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 38 | if (nickname != null) { 39 | checkNickname(nickname) 40 | } 41 | } 42 | 43 | override fun afterTextChanged(p0: Editable?) {} 44 | }) 45 | 46 | setOnClickListener() 47 | } 48 | 49 | 50 | private fun setOnClickListener() { 51 | binding.btnSignOut.setOnClickListener { 52 | signOutViewModel.signOut() 53 | 54 | lifecycleScope.launch { 55 | signOutViewModel.uiState.collectLatest { 56 | if (it.isSignOuted) { 57 | showToast(it.toastMessage) //Todo 사용가능 토스트가 무슨 3번이나 나옴 58 | } else { 59 | showToast(it.toastMessage) //Todo 사용가능 토스트가 무슨 3번이나 나옴 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | fun checkNickname(nickname: String) { 67 | //입력값 담기 68 | inputNickname = binding.etEnterNickname.text.trim().toString() 69 | // 값 유무에 따른 활성화 여부 70 | if (inputNickname == nickname) { 71 | binding.btnSignOut.isEnabled = true 72 | } else { 73 | binding.btnSignOut.isEnabled = false 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/mypage/terms/WebViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.mypage.terms 2 | 3 | import android.os.Bundle 4 | import android.webkit.WebViewClient 5 | import com.eatssu.android.databinding.ActivityWebviewBinding 6 | import com.eatssu.android.presentation.base.BaseActivity 7 | import timber.log.Timber 8 | 9 | 10 | class WebViewActivity : BaseActivity(ActivityWebviewBinding::inflate) { 11 | 12 | 13 | private var URL = "" 14 | private var TITLE = "" 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | 20 | 21 | binding.webview.apply { 22 | webViewClient = WebViewClient() 23 | settings.javaScriptEnabled = true 24 | 25 | // localStorage 사용 시 26 | // webView.settings.domStorageEnabled = true 27 | 28 | URL = intent.getStringExtra("URL") ?: "" //Todo 뷰모델 사용하도록 수정? 29 | TITLE = intent.getStringExtra("TITLE") ?: "" 30 | 31 | toolbarTitle.text = TITLE 32 | Timber.d(URL + TITLE) 33 | 34 | if (savedInstanceState != null) restoreState(savedInstanceState) 35 | else loadUrl(URL) 36 | } 37 | } 38 | 39 | override fun onBackPressed() { 40 | if (binding.webview.canGoBack()) binding.webview.goBack() 41 | else super.onBackPressed() 42 | } 43 | 44 | override fun onSaveInstanceState(outState: Bundle) { 45 | super.onSaveInstanceState(outState) 46 | binding.webview.saveState(outState) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/util/ActivityUtil.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.util 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | inline fun AppCompatActivity.startActivity(block: Intent.() -> Unit = {}) { 8 | startActivity(Intent(this, T::class.java).apply(block)) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/util/CalendarUtil.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.util 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import java.time.DayOfWeek 6 | import java.time.LocalDate 7 | import java.time.format.DateTimeFormatter 8 | 9 | 10 | object CalendarUtil { 11 | lateinit var selectedDate: LocalDate 12 | 13 | @RequiresApi(Build.VERSION_CODES.O) 14 | fun monthYearFromDate(date: LocalDate): String { 15 | val formatter = DateTimeFormatter.ofPattern("yyyy.MM") 16 | return date.format(formatter) 17 | } 18 | 19 | @RequiresApi(Build.VERSION_CODES.O) 20 | fun daysInWeekArray(selectedDate: LocalDate): ArrayList { 21 | val days = ArrayList() 22 | var current = sundayForDate(selectedDate) 23 | val endDate = current!!.plusWeeks(1) 24 | while (current!!.isBefore(endDate)) { 25 | days.add(current) 26 | current = current.plusDays(1) 27 | } 28 | return days 29 | } 30 | 31 | @RequiresApi(Build.VERSION_CODES.O) 32 | private fun sundayForDate(current: LocalDate): LocalDate? { 33 | var current = current 34 | val oneWeekAgo = current.minusWeeks(1) 35 | while (current.isAfter(oneWeekAgo)) { 36 | if (current.dayOfWeek == DayOfWeek.SUNDAY) return current 37 | current = current.minusDays(1) 38 | } 39 | return null 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eatssu/android/presentation/util/ContextUtil.kt: -------------------------------------------------------------------------------- 1 | package com.eatssu.android.presentation.util 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | import androidx.fragment.app.Fragment 6 | 7 | // Activity 8 | fun Context.showToast(msg: String) { 9 | //Todo 앱 진입시 빈 토스트 왜 뜨는지 알아야함 10 | if (msg.isNotEmpty()) { 11 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 12 | } 13 | } 14 | 15 | // Fragment 16 | fun Fragment.showToast(msg: String) { 17 | if (msg.isNotEmpty()) { 18 | Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/res/color/selector_bottom_navi_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/color/selector_calendar_colortext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_add_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable-v24/ic_add_pic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_bell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable-v24/ic_bell.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable-v24/ic_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable-v24/ic_profile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable-v24/ic_review.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable-v24/ic_setting.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alarm_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_arrow_left.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_arrow_right.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bad_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_bad_28.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_camera_alt_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cafeteria_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_check_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_good_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_good_28.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_info_12.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_kakao_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_kakao_login.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_left_arrow.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_map.xml: -------------------------------------------------------------------------------- 1 | 6 | 14 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_menu_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mypage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_none_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_none_review.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pencil.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_right_arrow.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_selector_background_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_three_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_three_dot.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_uncheck_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_uncheck_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_unselected_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_unselected_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_unsubscribe_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/ic_unsubscribe_16.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_backgroud_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_backgroud_snow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_dodam.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_dodam.jpeg -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_kakao_login_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_kakao_login_btn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_logo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_logo_512.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_logo_christmas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_logo_christmas.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_logo_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_logo_snow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_member.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_new_logo_primary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_new_logo_primary.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_new_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_new_logo_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_splash_christmas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_splash_christmas.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_splash_server_fix_contents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/drawable/img_splash_server_fix_contents.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/layer_bottom_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/layer_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_background_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_check_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_toggle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_button_duplicate.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_cafeteria_section.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_corner_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_corner_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_edittext_small_gray.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_menu_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_report_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_corners_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 14 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_text_field_small.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_text_field_small_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_toggle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_transparent_calendar_element.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/font-v26/font.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_black.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_extrabold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_extrabold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_extralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_extralight.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_semibold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EAT-SSU/Android/cceb7aafe546c860b31e97d7549fd9190e4a3a00/app/src/main/res/font/pretendard_thin.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_base.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 16 | 30 | 31 | 40 | 41 | 42 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_developer.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 21 | 22 | 28 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_intro.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_others_review_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_review_write_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 31 | 32 | 39 | 40 |