From 3b1c629cceca94e48055d2d9520acd0e14bfe2cb Mon Sep 17 00:00:00 2001 From: Hyeonmin Kim <101093716+khyeonm@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:18:19 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B6=80=EC=82=B0=EB=8C=80=20Android=5F?= =?UTF-8?q?=EA=B9=80=ED=98=84=EB=AF=BC=206=EC=A3=BC=EC=B0=A8=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=20Step0=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error * 부산대 Android_김현민 4주차 과제 Step0 (#16) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error --------- Co-authored-by: MyStoryG * Docs: Update README.md * Feat: Add location marker * Feat: Add camera movement function * Feat: Add bottom sheet dialog * Feat: Add initial camera location function * Refactor: Resolve initial camera issue and search issue * Feat: Add map error event * Design: Modify UI design * Refactor: Separate function and write annotations * Docs: Update README.md * Feat: Add save item click function * Feat: Add keyword search function * Refactor: Reflect step1 feedback * Initial commit * Feat: Add dependencies * Test: Add map activity ui test * Test: Add main activity ui test * Test: Add main activity unit test * Test: Add map activity unit test * 부산대 Android_김현민 4주차 과제 Step1 (#33) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error * Docs: Update README.md * Feat: Add location marker * Feat: Add camera movement function * Feat: Add bottom sheet dialog * Feat: Add initial camera location function * Refactor: Resolve initial camera issue and search issue * Feat: Add map error event * Design: Modify UI design * Refactor: Separate function and write annotations * Docs: Update README.md * Feat: Add save item click function * Feat: Add keyword search function * Update README.md --------- Co-authored-by: MyStoryG * Fix: Fix conflict error * Chore: Add dependency and grouping * 부산대 Android_김현민 5주차 과제 Step0 (#6) * Initial commit * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error * 부산대 Android_김현민 4주차 과제 Step0 (#16) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error --------- Co-authored-by: MyStoryG * Docs: Update README.md * Feat: Add location marker * Feat: Add camera movement function * Feat: Add bottom sheet dialog * Feat: Add initial camera location function * Refactor: Resolve initial camera issue and search issue * Feat: Add map error event * Design: Modify UI design * Refactor: Separate function and write annotations * Docs: Update README.md * Feat: Add save item click function * Feat: Add keyword search function * Refactor: Reflect step1 feedback * Feat: Add dependencies * Test: Add map activity ui test * Test: Add main activity ui test * Test: Add main activity unit test * Test: Add map activity unit test * 부산대 Android_김현민 4주차 과제 Step1 (#33) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error * Docs: Update README.md * Feat: Add location marker * Feat: Add camera movement function * Feat: Add bottom sheet dialog * Feat: Add initial camera location function * Refactor: Resolve initial camera issue and search issue * Feat: Add map error event * Design: Modify UI design * Refactor: Separate function and write annotations * Docs: Update README.md * Feat: Add save item click function * Feat: Add keyword search function * Update README.md --------- Co-authored-by: MyStoryG * Fix: Fix conflict error * Chore: Add dependency and grouping --------- Co-authored-by: MyStoryG * Chore: Edit grouping * Refactor: Change database to Room * Fix: Resolve data integrity error * Refactor: Apply dependency injection dependency injection about network * Refactor: Apply dependency injection dependency injection about map utility * Refactor: Apply dependency injection dependency injection about map search service * Chore: Edit grouping * Refactor: Apply dependency injection dependency injection about search save service * Refactor: Apply dependency injection dependency injection about bottomsheet * Fix: Remove test code error Resolve errors caused by code modification during refactoring process * Refactor: Reflect step1 feedback * Chore: Add initial settings * Refactor: Apply databinding in MainActivity * Refactor: Apply MVVM in error activity Utilize LiveData and DataBinding * 부산대 Android_김현민 5주차 과제 Step1 (#25) * Initial commit * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error * 부산대 Android_김현민 4주차 과제 Step0 (#16) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error --------- Co-authored-by: MyStoryG * Docs: Update README.md * Feat: Add location marker * Feat: Add camera movement function * Feat: Add bottom sheet dialog * Feat: Add initial camera location function * Refactor: Resolve initial camera issue and search issue * Feat: Add map error event * Design: Modify UI design * Refactor: Separate function and write annotations * Docs: Update README.md * Feat: Add save item click function * Feat: Add keyword search function * Refactor: Reflect step1 feedback * Feat: Add dependencies * Test: Add map activity ui test * Test: Add main activity ui test * Test: Add main activity unit test * Test: Add map activity unit test * 부산대 Android_김현민 4주차 과제 Step1 (#33) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md * Initial commit * Fix: Fix Week2 error * Docs: Update README.md Step1 README * Feat: Add initial settings * Feat: Add Document data class * Feat: Add RetrofitService interface * Refactor: Delete DB code * Feat: Add search function using API * Feat: Separate CategoryGroupCode * Docs: Update step2 README.md * Feat: complete initial project setup * Feat: Add mapview and map function * Fix: resolve errors * Chore: Update network security configuration * Fix: Resolve windows emulator issue * Style: Update mapview design * 부산대 Android_김현민 3주차 과제 Step0 (#19) * Initial commit * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Docs: Update README.md * Feat: Create item_view * Feat: Modify main view * Feat: Implement search list * Feat: Complete saved element view * Feat: Complete save action * Feat: Add persistent save function * 부산대 Android_김현민_2주차_과제_Step1 (#2) * Docs: Update README.md * Feat: Complete search screen layout * Feat: Add data to the database * Design: Modify save element design * Update README.md --------- Co-authored-by: MyStoryG * Fix: Reflect feedback * Fix: Resolve error * Docs: Update README.md * Feat: Add location marker * Feat: Add camera movement function * Feat: Add bottom sheet dialog * Feat: Add initial camera location function * Refactor: Resolve initial camera issue and search issue * Feat: Add map error event * Design: Modify UI design * Refactor: Separate function and write annotations * Docs: Update README.md * Feat: Add save item click function * Feat: Add keyword search function * Update README.md --------- Co-authored-by: MyStoryG * Fix: Fix conflict error * Chore: Add dependency and grouping * Chore: Edit grouping * Refactor: Change database to Room * Fix: Resolve data integrity error * Refactor: Apply dependency injection dependency injection about network * Refactor: Apply dependency injection dependency injection about map utility * Refactor: Apply dependency injection dependency injection about map search service * Chore: Edit grouping * Refactor: Apply dependency injection dependency injection about search save service * Refactor: Apply dependency injection dependency injection about bottomsheet * Fix: Remove test code error Resolve errors caused by code modification during refactoring process --------- Co-authored-by: MyStoryG * Refactor: Change asynchronous to coroutine * Refactor: Apply MVVM in map activity Utilize LiveData and DataBinding * Chore: Modify error activity code * Refactor: Apply MVVM in main activity Utilize LiveData and DataBinding * Chore: Prepare step0 --------- Co-authored-by: MyStoryG --- app/build.gradle.kts | 66 +++++++-- .../campus/tech/kakao/map/MainActivityTest.kt | 93 +++++++++++++ .../campus/tech/kakao/map/MainUnitTest.kt | 57 ++++++++ .../campus/tech/kakao/map/MapActivityTest.kt | 30 +++++ .../java/campus/tech/kakao/map/MapUnitTest.kt | 48 +++++++ app/src/main/AndroidManifest.xml | 23 +++- .../campus/tech/kakao/map/MainActivity.kt | 11 -- .../campus/tech/kakao/map/adapter/Adapter.kt | 51 +++++++ .../campus/tech/kakao/map/data/AppDatabase.kt | 12 ++ .../campus/tech/kakao/map/data/DBHelper.kt | 84 ++++++++++++ .../tech/kakao/map/data/KakaoResponse.kt | 22 +++ .../campus/tech/kakao/map/data/Profile.kt | 15 +++ .../campus/tech/kakao/map/data/ProfileDao.kt | 15 +++ .../tech/kakao/map/di/BottomSheetModule.kt | 20 +++ .../tech/kakao/map/di/DatabaseModule.kt | 26 ++++ .../campus/tech/kakao/map/di/MapModule.kt | 18 +++ .../campus/tech/kakao/map/di/NetworkModule.kt | 40 ++++++ .../campus/tech/kakao/map/di/SaveModule.kt | 21 +++ .../campus/tech/kakao/map/network/Document.kt | 18 +++ .../tech/kakao/map/network/KakaoResponse.kt | 20 +++ .../campus/tech/kakao/map/network/Network.kt | 29 ++++ .../tech/kakao/map/network/RetrofitService.kt | 30 +++++ .../tech/kakao/map/network/SearchService.kt | 31 +++++ .../campus/tech/kakao/map/ui/ErrorActivity.kt | 24 ++++ .../tech/kakao/map/ui/ErrorViewModel.kt | 13 ++ .../campus/tech/kakao/map/ui/MainActivity.kt | 108 +++++++++++++++ .../campus/tech/kakao/map/ui/MainViewModel.kt | 64 +++++++++ .../campus/tech/kakao/map/ui/MapActivity.kt | 126 ++++++++++++++++++ .../campus/tech/kakao/map/ui/MapViewModel.kt | 16 +++ .../kakao/map/utility/BottomSheetHelper.kt | 22 +++ .../kakao/map/utility/CategoryGroupCode.kt | 46 +++++++ .../campus/tech/kakao/map/utility/Document.kt | 18 +++ .../tech/kakao/map/utility/MapApplication.kt | 16 +++ .../tech/kakao/map/utility/MapUtility.kt | 31 +++++ .../campus/tech/kakao/map/utility/Profile.kt | 9 ++ .../tech/kakao/map/utility/SaveHelper.kt | 94 +++++++++++++ app/src/main/res/drawable/close.png | Bin 0 -> 823 bytes app/src/main/res/drawable/location.png | Bin 0 -> 3396 bytes app/src/main/res/drawable/refresh.png | Bin 0 -> 2274 bytes app/src/main/res/drawable/search.png | Bin 0 -> 2174 bytes app/src/main/res/layout/activity_error.xml | 51 +++++++ .../main/res/layout/activity_item_view.xml | 64 +++++++++ app/src/main/res/layout/activity_main.xml | 84 +++++++++--- app/src/main/res/layout/activity_map.xml | 47 +++++++ app/src/main/res/layout/bottom_sheet.xml | 32 +++++ app/src/main/res/layout/search_save.xml | 29 ++++ .../main/res/xml/network_security_config.xml | 6 + gradle/wrapper/gradle-wrapper.properties | 2 + settings.gradle.kts | 2 + 49 files changed, 1645 insertions(+), 39 deletions(-) create mode 100644 app/src/androidTest/java/campus/tech/kakao/map/MainActivityTest.kt create mode 100644 app/src/androidTest/java/campus/tech/kakao/map/MainUnitTest.kt create mode 100644 app/src/androidTest/java/campus/tech/kakao/map/MapActivityTest.kt create mode 100644 app/src/androidTest/java/campus/tech/kakao/map/MapUnitTest.kt delete mode 100644 app/src/main/java/campus/tech/kakao/map/MainActivity.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/adapter/Adapter.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/data/AppDatabase.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/data/DBHelper.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/data/KakaoResponse.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/data/Profile.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/data/ProfileDao.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/di/BottomSheetModule.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/di/MapModule.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/di/NetworkModule.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/di/SaveModule.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/network/Document.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/network/KakaoResponse.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/network/Network.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/network/RetrofitService.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/network/SearchService.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/ui/ErrorActivity.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/ui/ErrorViewModel.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/ui/MainActivity.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/ui/MainViewModel.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/ui/MapActivity.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/ui/MapViewModel.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/BottomSheetHelper.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/CategoryGroupCode.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/Document.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/MapApplication.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/MapUtility.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/Profile.kt create mode 100644 app/src/main/java/campus/tech/kakao/map/utility/SaveHelper.kt create mode 100644 app/src/main/res/drawable/close.png create mode 100644 app/src/main/res/drawable/location.png create mode 100644 app/src/main/res/drawable/refresh.png create mode 100644 app/src/main/res/drawable/search.png create mode 100644 app/src/main/res/layout/activity_error.xml create mode 100644 app/src/main/res/layout/activity_item_view.xml create mode 100644 app/src/main/res/layout/activity_map.xml create mode 100644 app/src/main/res/layout/bottom_sheet.xml create mode 100644 app/src/main/res/layout/search_save.xml create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 803085bd..5618bd3e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,10 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + + +fun getApiKey(key: String): String { + return gradleLocalProperties(rootDir, providers).getProperty(key) ?: "" +} + plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -12,6 +19,9 @@ android { namespace = "campus.tech.kakao.map" compileSdk = 34 + dataBinding { + enable = true + } defaultConfig { applicationId = "campus.tech.kakao.map" minSdk = 26 @@ -19,7 +29,23 @@ android { versionCode = 1 versionName = "1.0" + + ndk { + abiFilters.add("arm64-v8a") + abiFilters.add("armeabi-v7a") + abiFilters.add("x86") + abiFilters.add("x86_64") + } + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "KAKAO_API_KEY", getApiKey("KAKAO_API_KEY")) + buildConfigField("String", "KAKAO_REST_API_KEY", getApiKey("KAKAO_REST_API_KEY")) + // manifest에서 사용하려고 만듦 + manifestPlaceholders["KAKAO_API_KEY"] = getApiKey("KAKAO_API_KEY") + } + + testOptions { + animationsDisabled = true } buildTypes { @@ -41,34 +67,42 @@ android { buildFeatures { dataBinding = true + viewBinding = true buildConfig = true } } dependencies { - + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3") + implementation("androidx.room:room-runtime:2.6.1") + implementation("androidx.activity:activity-ktx:1.9.0") + implementation("androidx.room:room-ktx:2.6.1") + testImplementation("androidx.room:room-testing:2.6.1") +// implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.13.1") +// implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.12.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.recyclerview:recyclerview:1.3.2") + implementation("androidx.datastore:datastore-preferences:1.0.0") implementation("com.squareup.retrofit2:retrofit:2.11.0") implementation("com.squareup.retrofit2:converter-gson:2.11.0") implementation("com.kakao.maps.open:android:2.9.5") - implementation("androidx.activity:activity-ktx:1.9.0") - implementation("androidx.test:core-ktx:1.6.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3") - implementation("androidx.room:room-runtime:2.6.1") - kapt("androidx.room:room-compiler:2.6.1") + implementation("androidx.activity:activity:1.8.0") +// implementation("com.kakao.sdk:v2-all:2.20.3") implementation("com.google.dagger:hilt-android:2.48.1") - kapt("com.google.dagger:hilt-compiler:2.48.1") - implementation("androidx.activity:activity-ktx:1.9.0") - implementation("androidx.room:room-ktx:2.6.1") implementation(platform("com.google.firebase:firebase-bom:33.1.2")) implementation("com.google.firebase:firebase-analytics-ktx") + implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-config-ktx:22.0.0") implementation("com.google.firebase:firebase-messaging-ktx:24.0.0") - testImplementation("androidx.room:room-testing:2.6.1") + kapt("com.google.dagger:hilt-compiler:2.48.1") + kapt("androidx.room:room-compiler:2.6.1") + implementation("androidx.test.espresso:espresso-contrib:3.6.1") + // implementation("androidx.test:core-ktx:1.5.0") + implementation("androidx.test:core-ktx:1.6.1") testImplementation("junit:junit:4.13.2") testImplementation("io.mockk:mockk-android:1.13.11") testImplementation("io.mockk:mockk-agent:1.13.11") @@ -76,10 +110,20 @@ dependencies { testImplementation("org.robolectric:robolectric:4.11.1") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + debugImplementation("androidx.fragment:fragment-testing:1.8.1") + androidTestUtil("androidx.test:orchestrator:1.4.2") +// androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.ext:truth:1.5.0") + androidTestImplementation("androidx.test:core:1.5.0") + androidTestImplementation("androidx.test:runner:1.5.2") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") +// androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +// androidTestImplementation("androidx.test:rules:1.5.0") androidTestImplementation("androidx.test:rules:1.6.1") androidTestImplementation("androidx.test.espresso:espresso-intents:3.6.1") - androidTestImplementation("com.google.dagger:hilt-android-testing:2.48.1") + testImplementation("com.google.dagger:hilt-android-testing:2.48.1") + kaptTest("com.google.dagger:hilt-android-compiler:2.48.1") kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.48.1") + androidTestImplementation("com.google.dagger:hilt-android-testing:2.48.1") } diff --git a/app/src/androidTest/java/campus/tech/kakao/map/MainActivityTest.kt b/app/src/androidTest/java/campus/tech/kakao/map/MainActivityTest.kt new file mode 100644 index 00000000..9d68a1fb --- /dev/null +++ b/app/src/androidTest/java/campus/tech/kakao/map/MainActivityTest.kt @@ -0,0 +1,93 @@ +package campus.tech.kakao.map + +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import campus.tech.kakao.map.ui.MainActivity +import campus.tech.kakao.map.ui.MapActivity +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MainActivityTest { + + @get:Rule + val mainActivity = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun 검색창에_검색어를_입력한다(){ + onView(withId(R.id.etSearch)) + .perform(replaceText("입력테스트")) + .check(matches(withText("입력테스트"))) + } + @Test + fun 검색창의_x버튼을_누르면_검색어가_삭제된다() { + onView(withId(R.id.etSearch)) + .perform(replaceText("삭제테스트")) + + onView(withId(R.id.btnClose)) + .perform(click()) + + onView(withId(R.id.etSearch)) + .check(matches(withText(""))) + } + @Test + fun 장소를_선택하면_지도가_펼쳐진다() { + Intents.init() + onView(withId(R.id.etSearch)).perform(replaceText("성심당본점")) + Thread.sleep(800) + onView(withId(R.id.recyclerView)) + .perform( + RecyclerViewActions.actionOnItemAtPosition( + 0, click() + ) + ) + Intents.intended(IntentMatchers.hasComponent(MapActivity::class.java.name)) + Intents.release() + } + + @Test + fun 검색어를_입력하면_목록이_나타난다() { + onView(withId(R.id.etSearch)) + .perform(replaceText("성")) + + Thread.sleep(1000) + + onView(withId(R.id.tvNoResult)) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) + } + + +// @Test +// fun 지도화면에서_뒤로가기를_두번누르면_검색화면이_펼쳐진다() { +// Intents.init() +// onView(withId(R.id.etSearch)).perform(replaceText("성심당본점")) +// Thread.sleep(1000) +// onView(withId(R.id.recyclerView)) +// .perform( +// RecyclerViewActions.actionOnItemAtPosition( +// 0, click() +// ) +// ) +// Intents.intended(IntentMatchers.hasComponent(MapActivity::class.java.name)) +// +// Thread.sleep(800) +// pressBack() +// pressBack() +// +// Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.name)) +// Intents.release() +// } +} \ No newline at end of file diff --git a/app/src/androidTest/java/campus/tech/kakao/map/MainUnitTest.kt b/app/src/androidTest/java/campus/tech/kakao/map/MainUnitTest.kt new file mode 100644 index 00000000..eb104d61 --- /dev/null +++ b/app/src/androidTest/java/campus/tech/kakao/map/MainUnitTest.kt @@ -0,0 +1,57 @@ +package campus.tech.kakao.map + +import android.content.Context +import android.content.SharedPreferences +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import campus.tech.kakao.map.ui.MainActivity +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MainUnitTest { + lateinit var sharedPreferences: SharedPreferences + @Before + fun before() { + val context = ApplicationProvider.getApplicationContext() + sharedPreferences = context.getSharedPreferences("SavedItems", Context.MODE_PRIVATE) + sharedPreferences.edit().clear().apply() + } + + @After + fun after() { + sharedPreferences.edit().clear().apply() + } +// refactoring 으로 인해 오류가 떠서 주석처리 +// @Test +// fun addAndRemoveSaveItem() { +// val scenario = ActivityScenario.launch(MainActivity::class.java) +// +// scenario.onActivity { activity -> +// +// activity.addSavedItem("성심당 본점") +// activity.removeSavedItem("성심당 본점") +// assertEquals(0, activity.llSave.childCount) +// } +// scenario.close() +// } +// @Test +// fun addAndLoadSaveItem() { +// val scenario = ActivityScenario.launch(MainActivity::class.java) +// +// scenario.onActivity { mainActivity -> +// mainActivity.addSavedItem("성심당") +// mainActivity.saveSavedItems() +// mainActivity.llSave.removeAllViews() +// mainActivity.loadSavedItems() +// +// assertEquals(1, mainActivity.llSave.childCount) +// } +// +// scenario.close() +// } +} \ No newline at end of file diff --git a/app/src/androidTest/java/campus/tech/kakao/map/MapActivityTest.kt b/app/src/androidTest/java/campus/tech/kakao/map/MapActivityTest.kt new file mode 100644 index 00000000..16ebbb0c --- /dev/null +++ b/app/src/androidTest/java/campus/tech/kakao/map/MapActivityTest.kt @@ -0,0 +1,30 @@ +package campus.tech.kakao.map + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import campus.tech.kakao.map.ui.MainActivity +import campus.tech.kakao.map.ui.MapActivity +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MapActivityTest { + @get:Rule + val mapActivity = ActivityScenarioRule(MapActivity::class.java) + + @Test + fun 지도화면에서_검색창을_누르면_목록화면으로_이동한다() { + Intents.init() + onView(withId(R.id.etSearch)) + .perform(click()) + Intents.intended(hasComponent(MainActivity::class.java.name)) + Intents.release() + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/campus/tech/kakao/map/MapUnitTest.kt b/app/src/androidTest/java/campus/tech/kakao/map/MapUnitTest.kt new file mode 100644 index 00000000..d2385754 --- /dev/null +++ b/app/src/androidTest/java/campus/tech/kakao/map/MapUnitTest.kt @@ -0,0 +1,48 @@ +package campus.tech.kakao.map + +import android.content.Context +import android.content.SharedPreferences +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import campus.tech.kakao.map.ui.MapActivity +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class MapUnitTest { + lateinit var sharedPreferences: SharedPreferences + + @Before + fun before() { + val context = ApplicationProvider.getApplicationContext() + sharedPreferences = context.getSharedPreferences("pref", Context.MODE_PRIVATE) + sharedPreferences.edit().clear().apply() + } + + @After + fun after() { + sharedPreferences.edit().clear().apply() + } + + @Test + fun exitAppAndSaveLocation() { + val scenario = ActivityScenario.launch(MapActivity::class.java) + + scenario.onActivity { activity -> + val latitude = 35.0 + val longitude = 129.0 + activity.saveData(latitude.toString(), longitude.toString()) + + activity.loadData() + + val savedLatitude = sharedPreferences.getString("latitude", null)?.toDoubleOrNull() + val savedLongitude = sharedPreferences.getString("longitude", null)?.toDoubleOrNull() + + assertEquals(latitude, savedLatitude) + assertEquals(longitude, savedLongitude) + } + + scenario.close() + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f2e7b45a..5b33cea8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,23 +8,42 @@ + + + + + + android:name=".ui.MapActivity" + android:exported="true" > + + + diff --git a/app/src/main/java/campus/tech/kakao/map/MainActivity.kt b/app/src/main/java/campus/tech/kakao/map/MainActivity.kt deleted file mode 100644 index 95b43803..00000000 --- a/app/src/main/java/campus/tech/kakao/map/MainActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package campus.tech.kakao.map - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} diff --git a/app/src/main/java/campus/tech/kakao/map/adapter/Adapter.kt b/app/src/main/java/campus/tech/kakao/map/adapter/Adapter.kt new file mode 100644 index 00000000..cbec2b99 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/adapter/Adapter.kt @@ -0,0 +1,51 @@ +package campus.tech.kakao.map.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import campus.tech.kakao.map.data.Profile +import campus.tech.kakao.map.databinding.ActivityItemViewBinding + +class Adapter(private val profiles: MutableList) : RecyclerView.Adapter() { + + interface OnItemClickListener { + fun onItemClick(name: String, address: String, latitude: String, longitude: String) + } + + var listener: OnItemClickListener? = null + + fun setOnItemClickListener(listener: OnItemClickListener) { + this.listener = listener + } + + inner class ProfileViewHolder(val binding: ActivityItemViewBinding) : RecyclerView.ViewHolder(binding.root) { + init { + itemView.setOnClickListener { + bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { position -> + val profile = profiles[position] + listener?.onItemClick(profile.name, profile.address, profile.latitude, profile.longitude) + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ActivityItemViewBinding.inflate(inflater, parent, false) + return ProfileViewHolder(binding) + } + + override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) { + val profile = profiles[position] + holder.binding.profile = profile + holder.binding.executePendingBindings() + } + + override fun getItemCount(): Int = profiles.size + + fun updateProfiles(newProfiles: List) { + profiles.clear() + profiles.addAll(newProfiles) + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/data/AppDatabase.kt b/app/src/main/java/campus/tech/kakao/map/data/AppDatabase.kt new file mode 100644 index 00000000..b075d595 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/AppDatabase.kt @@ -0,0 +1,12 @@ +package campus.tech.kakao.map.data + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +@Database(entities = [Profile::class], version = 2) +abstract class AppDatabase : RoomDatabase() { + abstract fun profileDao(): ProfileDao +} + diff --git a/app/src/main/java/campus/tech/kakao/map/data/DBHelper.kt b/app/src/main/java/campus/tech/kakao/map/data/DBHelper.kt new file mode 100644 index 00000000..83106ac6 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/DBHelper.kt @@ -0,0 +1,84 @@ +//package campus.tech.kakao.map +// +//import android.content.Context +//import android.database.Cursor +//import android.database.sqlite.SQLiteDatabase +//import android.database.sqlite.SQLiteOpenHelper +// +//private const val TABLE_PLACE = "places" +//private const val COLUMN_TYPE = "type" +//private const val COLUMN_NAME = "name" +//private const val COLUMN_ADDRESS = "address" +// +//class DBHelper(context: Context) : SQLiteOpenHelper(context, "place.db", null, 1) { +// override fun onCreate(db: SQLiteDatabase?) { +// db?.execSQL( +// "CREATE TABLE $TABLE_PLACE (" + +// "$COLUMN_TYPE VARCHAR(30) NOT NULL," + +// "$COLUMN_NAME VARCHAR(30) NOT NULL," + +// "$COLUMN_ADDRESS VARCHAR(30) NOT NULL" + +// ");" +// ) +// +// insertPharData(db) +// insertCafeData(db) +// +// } +// +// override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { +// db?.execSQL("DROP TABLE IF EXISTS $TABLE_PLACE") +// onCreate(db) +// } +// +// private fun insertPharData(db: SQLiteDatabase?) { +// if (db == null) { +// return +// } +// val type = "약국" +// val name = "약국" +// val address = "서울 강남구 대치동" +// +// for (i in 1..30) { +// val nameWithIndex = "$name$i" +// val addressWithIndex = "$address $i" +// db?.execSQL("INSERT INTO $TABLE_PLACE ($COLUMN_TYPE, $COLUMN_NAME, $COLUMN_ADDRESS) VALUES ('$type', '$nameWithIndex', '$addressWithIndex');") +// } +// } +// +// private fun insertCafeData(db: SQLiteDatabase?) { +// if (db == null) { +// return +// } +// +// val type = "카페" +// val name = "카페" +// val address = "서울 성동구 성수동" +// +// for (i in 1..30) { +// val nameWithIndex = "$name$i" +// val addressWithIndex = "$address $i" +// db?.execSQL("INSERT INTO $TABLE_PLACE ($COLUMN_TYPE, $COLUMN_NAME, $COLUMN_ADDRESS) VALUES ('$type', '$nameWithIndex', '$addressWithIndex');") +// +// } +// } +// +// fun searchProfiles(query: String): List { +// val profiles = mutableListOf() +// val db = this.readableDatabase +// val cursor: Cursor = db.rawQuery( +// "SELECT * FROM $TABLE_PLACE WHERE $COLUMN_NAME LIKE ? OR $COLUMN_ADDRESS LIKE ? OR $COLUMN_TYPE LIKE ?", +// arrayOf("%$query%", "%$query%", "%$query%") +// ) +// +// if (cursor.moveToFirst()) { +// do { +// val type = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_TYPE)) +// val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)) +// val address = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ADDRESS)) +// profiles.add(Profile(name, address, type)) +// } while (cursor.moveToNext()) +// } +// cursor.close() +// return profiles +// } +//} diff --git a/app/src/main/java/campus/tech/kakao/map/data/KakaoResponse.kt b/app/src/main/java/campus/tech/kakao/map/data/KakaoResponse.kt new file mode 100644 index 00000000..09e14cc9 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/KakaoResponse.kt @@ -0,0 +1,22 @@ +package campus.tech.kakao.map.data + +import campus.tech.kakao.map.utility.Document + +data class KakaoResponse( +// val meta: Meta, + val documents: List +) + +//data class Meta( +// val total_count: Int, +// val pageable_count: Int, +// val is_end: Boolean, +// val same_name: SameName +//) + +//data class SameName( +// val region: List, +// val keyword: String, +// val selected_region: String +//) + diff --git a/app/src/main/java/campus/tech/kakao/map/data/Profile.kt b/app/src/main/java/campus/tech/kakao/map/data/Profile.kt new file mode 100644 index 00000000..190ed8ba --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/Profile.kt @@ -0,0 +1,15 @@ +package campus.tech.kakao.map.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName= "Profiles") +data class Profile ( + @PrimaryKey(autoGenerate = true) val uid: Int = 0, + @ColumnInfo(name = "name") val name: String, + @ColumnInfo(name = "address") val address: String, + @ColumnInfo(name = "type") val type: String, + @ColumnInfo(name = "latitude") val latitude: String, + @ColumnInfo(name = "longitude") val longitude: String +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/data/ProfileDao.kt b/app/src/main/java/campus/tech/kakao/map/data/ProfileDao.kt new file mode 100644 index 00000000..682f5bfa --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/ProfileDao.kt @@ -0,0 +1,15 @@ +package campus.tech.kakao.map.data + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query + +@Dao +interface ProfileDao { + @Query("SELECT * FROM profiles") + fun getAll(): List + + @Insert + fun insertAll(vararg profiles: Profile) + +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/di/BottomSheetModule.kt b/app/src/main/java/campus/tech/kakao/map/di/BottomSheetModule.kt new file mode 100644 index 00000000..ef166759 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/di/BottomSheetModule.kt @@ -0,0 +1,20 @@ +package campus.tech.kakao.map.di + +import android.content.Context +import campus.tech.kakao.map.utility.BottomSheetHelper +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped + +@Module +@InstallIn(ActivityComponent::class) +object BottomSheetModule { + @Provides + @ActivityScoped + fun singletonBottomSheet(@ActivityContext context: Context): BottomSheetHelper { + return BottomSheetHelper(context) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt b/app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt new file mode 100644 index 00000000..1ca49344 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt @@ -0,0 +1,26 @@ +package campus.tech.kakao.map.di + +import android.content.Context +import androidx.room.Room +import campus.tech.kakao.map.data.AppDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + @Singleton + fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase { + return Room.databaseBuilder( + appContext, + AppDatabase::class.java, + "profiles" + ).fallbackToDestructiveMigration().build() + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/di/MapModule.kt b/app/src/main/java/campus/tech/kakao/map/di/MapModule.kt new file mode 100644 index 00000000..9352ff28 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/di/MapModule.kt @@ -0,0 +1,18 @@ +package campus.tech.kakao.map.di + +import campus.tech.kakao.map.utility.MapUtility +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object MapModule { + @Provides + @Singleton + fun singletonMapUtiliy(): MapUtility { + return MapUtility + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/di/NetworkModule.kt b/app/src/main/java/campus/tech/kakao/map/di/NetworkModule.kt new file mode 100644 index 00000000..a2a10ddd --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/di/NetworkModule.kt @@ -0,0 +1,40 @@ +package campus.tech.kakao.map.di + +import android.content.Context +import campus.tech.kakao.map.network.Network +import campus.tech.kakao.map.network.RetrofitService +import campus.tech.kakao.map.network.SearchService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + @Provides + @Singleton + fun singletonRetrofitService(): RetrofitService { + return Retrofit.Builder() + .baseUrl("https://dapi.kakao.com") + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(RetrofitService::class.java) + } + + @Provides + @Singleton + fun singletonNetwork(retrofitService: RetrofitService): Network { + return Network(retrofitService) + } + + @Provides + @Singleton + fun singletonSearch(network: Network, @ApplicationContext context: Context): SearchService { + return SearchService(network, context) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/di/SaveModule.kt b/app/src/main/java/campus/tech/kakao/map/di/SaveModule.kt new file mode 100644 index 00000000..01b8ccaa --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/di/SaveModule.kt @@ -0,0 +1,21 @@ +package campus.tech.kakao.map.di + +import android.content.Context +import campus.tech.kakao.map.utility.SaveHelper +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object SaveModule { + + @Provides + @Singleton + fun provideSearchSaveHelper(@ApplicationContext context: Context): SaveHelper { + return SaveHelper(context) + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/network/Document.kt b/app/src/main/java/campus/tech/kakao/map/network/Document.kt new file mode 100644 index 00000000..60d459b9 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/network/Document.kt @@ -0,0 +1,18 @@ +package campus.tech.kakao.map.network + +import com.google.gson.annotations.SerializedName + +data class Document( +// val id: String, + @SerializedName("place_name") val name: String, + @SerializedName("category_group_name") val type: String, +// val category_group_code: String, +// val category_group_name: String, +// val phone: String, +// val address_name: String, + @SerializedName("road_address_name") val address: String, + @SerializedName("x") val longitude: String, // 경도 + @SerializedName("y") val latitude: String, // 위도 +// val place_url: String, +// val distance: String +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/network/KakaoResponse.kt b/app/src/main/java/campus/tech/kakao/map/network/KakaoResponse.kt new file mode 100644 index 00000000..97382f92 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/network/KakaoResponse.kt @@ -0,0 +1,20 @@ +package campus.tech.kakao.map.network + +data class KakaoResponse( +// val meta: Meta, + val documents: List +) + +//data class Meta( +// val total_count: Int, +// val pageable_count: Int, +// val is_end: Boolean, +// val same_name: SameName +//) + +//data class SameName( +// val region: List, +// val keyword: String, +// val selected_region: String +//) + diff --git a/app/src/main/java/campus/tech/kakao/map/network/Network.kt b/app/src/main/java/campus/tech/kakao/map/network/Network.kt new file mode 100644 index 00000000..7ec63cfc --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/network/Network.kt @@ -0,0 +1,29 @@ +package campus.tech.kakao.map.network + +import campus.tech.kakao.map.BuildConfig +import retrofit2.Callback +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Inject + +class Network @Inject constructor(private val retrofitService: RetrofitService) { + val API_KEY = "KakaoAK ${BuildConfig.KAKAO_REST_API_KEY}" + + suspend fun searchCategory(categoryGroupCode: String): KakaoResponse? { + val response = retrofitService.getSearchCategory(API_KEY, categoryGroupCode) + if (response.isSuccessful) { + return response.body() + } else { + throw Exception("응답 실패: ${response.errorBody()?.string()}") + } + } + + suspend fun searchKeyword(query: String): KakaoResponse? { + val response = retrofitService.getSearchKeyword(API_KEY, query) + if (response.isSuccessful) { + return response.body() + } else { + throw Exception("응답 실패: ${response.errorBody()?.string()}") + } + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/network/RetrofitService.kt b/app/src/main/java/campus/tech/kakao/map/network/RetrofitService.kt new file mode 100644 index 00000000..2cf6f3a4 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/network/RetrofitService.kt @@ -0,0 +1,30 @@ +package campus.tech.kakao.map.network + +import retrofit2.Call +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface RetrofitService { + @GET("/v2/local/search/category.json") + suspend fun getSearchCategory( + @Header("Authorization") apiKey: String, + @Query("category_group_code") categoryGroupCode: String, +// @Query("x") x: String, +// @Query("y") y: String, +// @Query("radius") radius: Int, + @Query("page") page: Int = 1, + @Query("size") size: Int = 15, + @Query("sort") sort: String = "accuracy" + ): Response + + @GET("/v2/local/search/keyword.json") + suspend fun getSearchKeyword( + @Header("Authorization") apiKey: String, + @Query("query") query: String, + @Query("page") page: Int = 1, + @Query("size") size: Int = 15, + @Query("sort") sort: String = "accuracy" + ): Response +} diff --git a/app/src/main/java/campus/tech/kakao/map/network/SearchService.kt b/app/src/main/java/campus/tech/kakao/map/network/SearchService.kt new file mode 100644 index 00000000..ea58bfeb --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/network/SearchService.kt @@ -0,0 +1,31 @@ +package campus.tech.kakao.map.network + +import android.content.Context +import android.util.Log +import android.widget.Toast +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import javax.inject.Inject +import javax.inject.Singleton + +class SearchService @Inject constructor(private val network: Network, private val context: Context) { + + suspend fun searchKeyword(query: String): KakaoResponse? { + return try { + network.searchKeyword(query) + } catch (error: Throwable) { + Log.e("SearchService", "요청 실패", error) + throw error + } + } + + suspend fun searchCategory(categoryGroupCode: String): KakaoResponse? { + return try { + network.searchCategory(categoryGroupCode) + } catch (error: Throwable) { + Log.e("SearchService", "요청 실패", error) + throw error + } + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/ui/ErrorActivity.kt b/app/src/main/java/campus/tech/kakao/map/ui/ErrorActivity.kt new file mode 100644 index 00000000..2b7955bb --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/ui/ErrorActivity.kt @@ -0,0 +1,24 @@ +package campus.tech.kakao.map.ui + +import android.os.Bundle +import android.widget.TextView +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import campus.tech.kakao.map.R +import campus.tech.kakao.map.databinding.ActivityErrorBinding + +class ErrorActivity : AppCompatActivity() { + val errorViewModel: ErrorViewModel by viewModels() + lateinit var errorBinding: ActivityErrorBinding + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + errorBinding = DataBindingUtil.setContentView(this, R.layout.activity_error) + errorBinding.lifecycleOwner = this + errorBinding.error = errorViewModel + + val errorMessage = intent.getStringExtra("errorMessage") + errorViewModel.setErrorMessage(errorMessage?:"Unknown") + + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/ui/ErrorViewModel.kt b/app/src/main/java/campus/tech/kakao/map/ui/ErrorViewModel.kt new file mode 100644 index 00000000..64ff12ae --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/ui/ErrorViewModel.kt @@ -0,0 +1,13 @@ +package campus.tech.kakao.map.ui + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class ErrorViewModel: ViewModel() { + val _errorMessage = MutableLiveData() + val errorMessage: LiveData get() = _errorMessage + fun setErrorMessage(message: String) { + _errorMessage.value = message + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/ui/MainActivity.kt b/app/src/main/java/campus/tech/kakao/map/ui/MainActivity.kt new file mode 100644 index 00000000..b4aa1365 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/ui/MainActivity.kt @@ -0,0 +1,108 @@ +package campus.tech.kakao.map.ui + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import campus.tech.kakao.map.adapter.Adapter +import campus.tech.kakao.map.data.AppDatabase +import campus.tech.kakao.map.databinding.ActivityMainBinding +import campus.tech.kakao.map.utility.SaveHelper +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +@AndroidEntryPoint +class MainActivity : AppCompatActivity() { + + @Inject + lateinit var db: AppDatabase + + @Inject + lateinit var searchSave: SaveHelper + + private val viewModel: MainViewModel by viewModels() + + private lateinit var mainBinding: ActivityMainBinding + private lateinit var adapter: Adapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mainBinding = ActivityMainBinding.inflate(layoutInflater) + setContentView(mainBinding.root) + + mainBinding.recyclerView.layoutManager = LinearLayoutManager(this) + adapter = Adapter(mutableListOf()) + mainBinding.recyclerView.adapter = adapter + + viewModel.profiles.observe(this, Observer { profiles -> + if (profiles.isEmpty()) { + showNoResults() + } else { + adapter.updateProfiles(profiles) + mainBinding.tvNoResult.visibility = View.GONE + + lifecycleScope.launch { + withContext(Dispatchers.IO) { + db.profileDao().insertAll(*profiles.toTypedArray()) + } + } + } + }) + + viewModel.noResult.observe(this, Observer { noResult -> + if (noResult) { + showNoResults() + } + }) + + mainBinding.etSearch.addTextChangedListener { text -> + val search = text.toString() + if (search.isNotEmpty()) { + viewModel.searchProfiles(search) + } else { + showNoResults() + } + } + + adapter.setOnItemClickListener(object : Adapter.OnItemClickListener { + override fun onItemClick(name: String, address: String, latitude: String, longitude: String) { + if (searchSave.isProfileInSearchSave(name, mainBinding.llSave)) { + searchSave.removeSavedItem(name, mainBinding.llSave) + } + searchSave.addSavedItem(name, mainBinding.llSave, mainBinding.hScrollView, LayoutInflater.from(this@MainActivity), mainBinding.etSearch) + val intent = Intent(this@MainActivity, MapActivity::class.java).apply { + putExtra("name", name) + putExtra("address", address) + putExtra("latitude", latitude) + putExtra("longitude", longitude) + } + startActivity(intent) + } + }) + + mainBinding.btnClose.setOnClickListener { + mainBinding.etSearch.text?.clear() + } + + searchSave.loadSavedItems(mainBinding.llSave, mainBinding.hScrollView, LayoutInflater.from(this), mainBinding.etSearch) + } + + private fun showNoResults() { + mainBinding.tvNoResult.visibility = View.VISIBLE + adapter.updateProfiles(emptyList()) + } + + override fun onPause() { + super.onPause() + searchSave.saveSavedItems(mainBinding.llSave) + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/ui/MainViewModel.kt b/app/src/main/java/campus/tech/kakao/map/ui/MainViewModel.kt new file mode 100644 index 00000000..6e38ecfc --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/ui/MainViewModel.kt @@ -0,0 +1,64 @@ +package campus.tech.kakao.map.ui + +import android.widget.Toast +import androidx.lifecycle.* +import campus.tech.kakao.map.data.Profile +import campus.tech.kakao.map.network.Document +import campus.tech.kakao.map.network.KakaoResponse +import campus.tech.kakao.map.network.SearchService +import campus.tech.kakao.map.utility.CategoryGroupCode +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor(private val searchService: SearchService) : ViewModel() { + + private val _profiles = MutableLiveData>() + val profiles: LiveData> get() = _profiles + + private val _noResult = MutableLiveData() + val noResult: LiveData get() = _noResult + + fun searchProfiles(search: String) { + viewModelScope.launch { + try { + val result = withContext(Dispatchers.IO) { searchService.searchKeyword(search) } + handleSearchResult(result) + } catch (error: Exception) { + _noResult.value = true + } + } + + CategoryGroupCode.categoryMap[search]?.let { categoryCode -> + viewModelScope.launch { + try { + val result = searchService.searchCategory(categoryCode) + handleSearchResult(result) + } catch (error: Exception) { + _noResult.value = true + + } + } + } + } + + private fun handleSearchResult(result: KakaoResponse?) { + result?.documents?.let { documents -> + if (documents.isEmpty()) { + _noResult.value = true + } else { + val profilesList = documents.map { it.toProfile() } + _profiles.value = profilesList + } + } ?: run { + _noResult.value = true + } + } + + private fun Document.toProfile(): Profile { + return Profile(name = this.name, address = this.address, type = this.type, latitude = this.latitude, longitude = this.longitude) + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/ui/MapActivity.kt b/app/src/main/java/campus/tech/kakao/map/ui/MapActivity.kt new file mode 100644 index 00000000..46187c0f --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/ui/MapActivity.kt @@ -0,0 +1,126 @@ +package campus.tech.kakao.map.ui + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.widget.EditText +import android.widget.TextView +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import campus.tech.kakao.map.R +import campus.tech.kakao.map.databinding.ActivityMapBinding +import campus.tech.kakao.map.utility.BottomSheetHelper +import campus.tech.kakao.map.utility.MapUtility +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.kakao.vectormap.KakaoMap +import com.kakao.vectormap.KakaoMapReadyCallback +import com.kakao.vectormap.LatLng +import com.kakao.vectormap.MapLifeCycleCallback +import com.kakao.vectormap.MapView +import com.kakao.vectormap.camera.CameraUpdateFactory +import com.kakao.vectormap.label.LabelOptions +import com.kakao.vectormap.label.LabelStyle +import com.kakao.vectormap.label.LabelStyles +import com.kakao.vectormap.utils.MapUtils +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class MapActivity : AppCompatActivity() { + + @Inject + lateinit var mapUtility: MapUtility + + @Inject + lateinit var bottomSheetHelper: BottomSheetHelper + + lateinit var mapBinding: ActivityMapBinding + lateinit var startMainActivityForResult: ActivityResultLauncher + var map: KakaoMap? = null + var savedLatitude: Double = 37.5642 + var savedLongitude: Double = 127.00 + val mapViewModel: MapViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mapBinding = DataBindingUtil.setContentView(this, R.layout.activity_map) + mapBinding.lifecycleOwner = this + mapBinding.mapViewModel = mapViewModel + + startMainActivityForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + } + } + + mapViewModel.searchClickEvent.observe(this, Observer { + val intent = Intent(this, MainActivity::class.java) + startMainActivityForResult.launch(intent) + }) + loadData() // 저장 위치 가져오기 + + mapBinding.mapView.start(object : MapLifeCycleCallback() { + override fun onMapDestroy() { + Log.d("KakaoMap", "onMapDestroy") + } + + override fun onMapError(error: Exception) { + val intent = Intent(this@MapActivity, ErrorActivity::class.java).apply{ + putExtra("errorMessage",error.toString()) + } + startActivity(intent) + } + }, object : KakaoMapReadyCallback() { + override fun onMapReady(kakaoMap: KakaoMap) { + Log.d("KakaoMap", "onMapReady") + map = kakaoMap + + mapUtility.camera(kakaoMap, savedLatitude, savedLongitude) + + // 장소 정보 받아옴 + val name = intent.getStringExtra("name") + val address = intent.getStringExtra("address") + val latitude = intent.getStringExtra("latitude")?.toDouble() + val longitude = intent.getStringExtra("longitude")?.toDouble() + if (latitude != null && longitude != null) { + mapUtility.addMarker(kakaoMap, latitude, longitude, name.toString()) + mapUtility.camera(kakaoMap, latitude, longitude) + + savedLatitude = latitude + savedLongitude = longitude + + bottomSheetHelper.showBottomSheet(name.toString(), address.toString()) + } + } + }) + } + override fun onResume() { + super.onResume() + mapBinding.mapView.resume() + } + + override fun onPause() { + super.onPause() + mapBinding.mapView.pause() + saveData(savedLatitude.toString(), savedLongitude.toString()) + } + + // 위도와 경도 저장, 가져오기 + fun saveData(latitude: String, longitude: String){ + val pref = getSharedPreferences("pref", 0) + val edit = pref.edit() + edit.putString("latitude", latitude) + edit.putString("longitude", longitude) + edit.apply() + } + + fun loadData() { + val pref = getSharedPreferences("pref", 0) + savedLatitude = pref.getString("latitude", "37.5642")?.toDouble() ?: 37.5642 + savedLongitude = pref.getString("longitude", "127.00")?.toDouble() ?: 127.00 + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/ui/MapViewModel.kt b/app/src/main/java/campus/tech/kakao/map/ui/MapViewModel.kt new file mode 100644 index 00000000..9c40ac71 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/ui/MapViewModel.kt @@ -0,0 +1,16 @@ +package campus.tech.kakao.map.ui + +import android.content.Intent +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class MapViewModel : ViewModel() { + + private val _searchClickEvent = MutableLiveData() + val searchClickEvent: LiveData get() = _searchClickEvent + + fun onSearchClick() { + _searchClickEvent.value = Unit + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/utility/BottomSheetHelper.kt b/app/src/main/java/campus/tech/kakao/map/utility/BottomSheetHelper.kt new file mode 100644 index 00000000..21ac6a2a --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/BottomSheetHelper.kt @@ -0,0 +1,22 @@ +package campus.tech.kakao.map.utility + +import android.content.Context +import android.view.LayoutInflater +import android.widget.TextView +import campus.tech.kakao.map.R +import com.google.android.material.bottomsheet.BottomSheetDialog +import javax.inject.Inject + +class BottomSheetHelper @Inject constructor (private val context: Context){ + + fun showBottomSheet(name: String, address: String) { + val bottomSheetDialog = BottomSheetDialog(context) + val bottomSheetLayout = LayoutInflater.from(context).inflate(R.layout.bottom_sheet, null) + bottomSheetDialog.setContentView(bottomSheetLayout) + val bottomName = bottomSheetLayout.findViewById(R.id.tvBName) + val bottomAddress = bottomSheetLayout.findViewById(R.id.tvBAddress) + bottomName.text= name + bottomAddress.text = address + bottomSheetDialog.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/utility/CategoryGroupCode.kt b/app/src/main/java/campus/tech/kakao/map/utility/CategoryGroupCode.kt new file mode 100644 index 00000000..6ffcca86 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/CategoryGroupCode.kt @@ -0,0 +1,46 @@ +package campus.tech.kakao.map.utility +object CategoryGroupCode { + val MART = "대형마트" to "MT1" + val CONVENIENCE_STORE = "편의점" to "CS2" + val DAYCARE_CENTER = "어린이집" to "PS3" + val KINDERGARTEN = "유치원" to "PS3" + val SCHOOL = "학교" to "SC4" + val ACADEMY = "학원" to "AC5" + val PARKING = "주차장" to "PK6" + val GAS_STATION = "주유소" to "OL7" + val FULL_STATION = "충전소" to "OL7" + val SUBWAY_STATION = "지하철역" to "SW8" + val BANK = "은행" to "BK9" + val CULTURE_FACILITY = "문화시설" to "CT1" + val AGENCY = "중개업소" to "AG2" + val PUBLIC_OFFICE = "공공기관" to "PO3" + val TOURIST_ATTRACTION = "관광명소" to "AT4" + val ACCOMMODATION = "숙박" to "AD5" + val RESTAURANT = "음식점" to "FD6" + val CAFE = "카페" to "CE7" + val HOSPITAL = "병원" to "HP8" + val PHARMACY = "약국" to "PM9" + + val categoryMap = mapOf( + MART, + CONVENIENCE_STORE, + DAYCARE_CENTER, + KINDERGARTEN, + SCHOOL, + ACADEMY, + PARKING, + GAS_STATION, + FULL_STATION, + SUBWAY_STATION, + BANK, + CULTURE_FACILITY, + AGENCY, + PUBLIC_OFFICE, + TOURIST_ATTRACTION, + ACCOMMODATION, + RESTAURANT, + CAFE, + HOSPITAL, + PHARMACY + ) +} diff --git a/app/src/main/java/campus/tech/kakao/map/utility/Document.kt b/app/src/main/java/campus/tech/kakao/map/utility/Document.kt new file mode 100644 index 00000000..c26fd415 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/Document.kt @@ -0,0 +1,18 @@ +package campus.tech.kakao.map.utility + +import com.google.gson.annotations.SerializedName + +data class Document( +// val id: String, + @SerializedName("place_name") val name: String, + @SerializedName("category_group_name") val type: String, +// val category_group_code: String, +// val category_group_name: String, +// val phone: String, +// val address_name: String, + @SerializedName("road_address_name") val address: String, + @SerializedName("x") val longitude: String, // 경도 + @SerializedName("y") val latitude: String, // 위도 +// val place_url: String, +// val distance: String +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/utility/MapApplication.kt b/app/src/main/java/campus/tech/kakao/map/utility/MapApplication.kt new file mode 100644 index 00000000..dceb1d08 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/MapApplication.kt @@ -0,0 +1,16 @@ +package campus.tech.kakao.map.utility + +import android.app.Application +import campus.tech.kakao.map.BuildConfig +import com.kakao.vectormap.KakaoMapSdk +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MapApplication: Application() { + override fun onCreate() { + super.onCreate() + KakaoMapSdk.init(this, BuildConfig.KAKAO_API_KEY) + } + +} + diff --git a/app/src/main/java/campus/tech/kakao/map/utility/MapUtility.kt b/app/src/main/java/campus/tech/kakao/map/utility/MapUtility.kt new file mode 100644 index 00000000..767d8661 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/MapUtility.kt @@ -0,0 +1,31 @@ +package campus.tech.kakao.map.utility + +import android.graphics.Color +import campus.tech.kakao.map.R +import com.kakao.vectormap.KakaoMap +import com.kakao.vectormap.LatLng +import com.kakao.vectormap.camera.CameraUpdateFactory +import com.kakao.vectormap.label.LabelOptions +import com.kakao.vectormap.label.LabelStyle +import com.kakao.vectormap.label.LabelStyles + +object MapUtility { + fun camera(kakaoMap: KakaoMap, latitude: Double, longitude: Double) { + val cameraUpdate = CameraUpdateFactory.newCenterPosition(LatLng.from(latitude, longitude)) + kakaoMap.moveCamera(cameraUpdate) + } + + fun addMarker(kakaoMap: KakaoMap, latitude: Double, longitude: Double, name: String): LabelOptions? { + val labelManager = kakaoMap.labelManager + val iconAndTextStyle = LabelStyles.from( + LabelStyle.from(R.drawable.location).setTextStyles(25, Color.BLACK) + ) + val options = LabelOptions.from(LatLng.from(latitude, longitude)) + .setStyles(iconAndTextStyle) + val layer = labelManager?.layer + val label = layer?.addLabel(options) + label?.changeText(name) + + return options + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/utility/Profile.kt b/app/src/main/java/campus/tech/kakao/map/utility/Profile.kt new file mode 100644 index 00000000..2493661b --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/Profile.kt @@ -0,0 +1,9 @@ +package campus.tech.kakao.map.utility + +data class Profile ( + val name: String, + val address: String, + val type: String, + val latitude: String, + val longitude: String +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/utility/SaveHelper.kt b/app/src/main/java/campus/tech/kakao/map/utility/SaveHelper.kt new file mode 100644 index 00000000..c4486fef --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/utility/SaveHelper.kt @@ -0,0 +1,94 @@ +package campus.tech.kakao.map.utility + +import android.content.Context +import android.content.SharedPreferences +import android.view.LayoutInflater +import android.view.View +import android.widget.EditText +import android.widget.HorizontalScrollView +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.widget.LinearLayoutCompat +import androidx.constraintlayout.widget.ConstraintLayout +import campus.tech.kakao.map.R +import org.json.JSONArray + +class SaveHelper (private val context: Context) { + + private val sharedPreferences: SharedPreferences = context.getSharedPreferences("SavedItems", Context.MODE_PRIVATE) + + fun saveSavedItems(llSave: LinearLayoutCompat) { + val editor = sharedPreferences.edit() + val savedNames = JSONArray() + for (i in 0 until llSave.childCount) { + val savedView = llSave.getChildAt(i) as? ConstraintLayout + val tvSaveName = savedView?.findViewById(R.id.tvSaveName) + if (tvSaveName != null) { + savedNames.put(tvSaveName.text.toString()) + } + } + editor.putString("savedNames", savedNames.toString()) + editor.apply() + } + + fun loadSavedItems(llSave: LinearLayoutCompat, hScrollView: HorizontalScrollView, inflater: LayoutInflater, etSearch: EditText) { + val savedNamesString = sharedPreferences.getString("savedNames", "[]") + val savedNames = JSONArray(savedNamesString) + for (i in 0 until savedNames.length()) { + val name = savedNames.getString(i) + addSavedItem(name, llSave, hScrollView, inflater, etSearch) + } + if (savedNames.length() > 0) { + hScrollView.visibility = View.VISIBLE + } + } + + fun addSavedItem(name: String, llSave: LinearLayoutCompat, hScrollView: HorizontalScrollView, inflater: LayoutInflater, etSearch: EditText) { + val savedView = inflater.inflate(R.layout.search_save, llSave, false) as ConstraintLayout + + val tvSaveName = savedView.findViewById(R.id.tvSaveName) + val ivDelete = savedView.findViewById(R.id.ivDelete) + + tvSaveName.text = name + + tvSaveName.setOnClickListener { + etSearch.setText(name) + } + + ivDelete.setOnClickListener { + llSave.removeView(savedView) + } + + llSave.addView(savedView) + hScrollView.visibility = View.VISIBLE + scrollToEndOfSearchSave(hScrollView) + } + + fun removeSavedItem(name: String, llSave: LinearLayoutCompat) { + for (i in 0 until llSave.childCount) { + val savedView = llSave.getChildAt(i) as? ConstraintLayout + val tvSaveName = savedView?.findViewById(R.id.tvSaveName) + if (tvSaveName?.text.toString() == name) { + llSave.removeViewAt(i) + break + } + } + } + + fun isProfileInSearchSave(name: String, llSave: LinearLayoutCompat): Boolean { + for (i in 0 until llSave.childCount) { + val savedView = llSave.getChildAt(i) as? ConstraintLayout + val tvSaveName = savedView?.findViewById(R.id.tvSaveName) + if (tvSaveName?.text.toString() == name) { + return true + } + } + return false + } + + private fun scrollToEndOfSearchSave(hScrollView: HorizontalScrollView) { + hScrollView.post { + hScrollView.fullScroll(View.FOCUS_RIGHT) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/close.png b/app/src/main/res/drawable/close.png new file mode 100644 index 0000000000000000000000000000000000000000..69428cf15b3123a8354c2a5f47e1782f975b635f GIT binary patch literal 823 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>VEX9k;uum9 z_jZnBP_u)8tK#vCzjvHJWxVs$z28S3AGs2e6|_R-jKST%_0jip6L$T!wF^iv^8eK= zG-Kh_(3(jM9~>1Jg&5Qr^wzU@Fw9`su}4UOF@fp9C$9#U2F`}l+V~{fRUTB-)LYKA z+jn4d+0_15H)9zT98ORBhhf1~!`xq|86J3<1s zyE!7{TcVj(r57`vXMZ@Q;myquhuaDb^mv`%n(uWWjx%-tr%V68-K%`v|9t!ZXi&)d zc7j8=fu(WHUR7lm24;pfxoasqxD%x2tepNk`Ow;qo|dS}r>y#N2R+Es#(9<7-SOM$WiEsMi{6wAua{_~>tDnm{r-UW|!xUJ~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/location.png b/app/src/main/res/drawable/location.png new file mode 100644 index 0000000000000000000000000000000000000000..632dcbc1346a6eadc599cfd1ebcc84e37f367b91 GIT binary patch literal 3396 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/search.png b/app/src/main/res/drawable/search.png new file mode 100644 index 0000000000000000000000000000000000000000..452055ef8c70104f5718a0369bbf52c4cb8e57e3 GIT binary patch literal 2174 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 A(*OVf literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_error.xml b/app/src/main/res/layout/activity_error.xml new file mode 100644 index 00000000..f1f87dfe --- /dev/null +++ b/app/src/main/res/layout/activity_error.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_item_view.xml b/app/src/main/res/layout/activity_item_view.xml new file mode 100644 index 00000000..98446ba4 --- /dev/null +++ b/app/src/main/res/layout/activity_item_view.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 24d17df2..7c4ffb94 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,19 +1,73 @@ - + xmlns:tools="http://schemas.android.com/tools"> + + + + - + - +