diff --git a/README.md b/README.md index 91dbd079..5fff9842 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ # android-map-location +## 1단계 기능 구현 목록 +- [x] 저장된 검색어 선택 시 검색 결과 표시 +- [x] 검색 목록에서 항목 선택 시 해당 항목의 위치 표시 + - [x] 핀과 이름을 표시 + - [x] BottomSheet를 통해 정보 표시 +- [x] 앱 종료 시 마지막 위치로 이동 +- [x] 맵을 불러오는데 실패하였을 때 대응 화면 표시 \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 250fba37..17a77e31 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { implementation("com.kakao.maps.open:android:2.9.5") implementation("androidx.activity:activity:1.8.0") implementation("androidx.test:core-ktx:1.5.0") + implementation("androidx.activity:activity-ktx:1.9.0") testImplementation("junit:junit:4.13.2") testImplementation("io.mockk:mockk-android:1.13.11") testImplementation("io.mockk:mockk-agent:1.13.11") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e1d5170f..e921fd5c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,11 +17,11 @@ android:theme="@style/Theme.Map" tools:targetApi="31"> diff --git a/app/src/main/java/campus/tech/kakao/map/DatabaseListener.kt b/app/src/main/java/campus/tech/kakao/map/DatabaseListener.kt index 463815b4..346724e2 100644 --- a/app/src/main/java/campus/tech/kakao/map/DatabaseListener.kt +++ b/app/src/main/java/campus/tech/kakao/map/DatabaseListener.kt @@ -1,6 +1,11 @@ package campus.tech.kakao.map +import campus.tech.kakao.map.domain.model.Location + interface DatabaseListener { - fun deleteHistory(historyName: String) - fun insertHistory(historyName: String) + fun deleteHistory(oldHistory: Location) + fun insertHistory(newHistory: Location) + fun searchHistory(locName: String, isExactMatch: Boolean) + fun showMap(newHistory: Location) + fun insertLastLocation(location: Location) } \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/Location.kt b/app/src/main/java/campus/tech/kakao/map/Location.kt deleted file mode 100644 index 0d893493..00000000 --- a/app/src/main/java/campus/tech/kakao/map/Location.kt +++ /dev/null @@ -1,14 +0,0 @@ -package campus.tech.kakao.map - -data class Location( - val name: String, - val category: String, - val address: String -) { - companion object { - const val CAFE: String = "카페" - const val PHARMACY: String = "약국" - const val RESTAURANT: String = "식당" - const val NORMAL: String = "일반" - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MapActivity.kt b/app/src/main/java/campus/tech/kakao/map/MapActivity.kt deleted file mode 100644 index ed89ee3e..00000000 --- a/app/src/main/java/campus/tech/kakao/map/MapActivity.kt +++ /dev/null @@ -1,50 +0,0 @@ -package campus.tech.kakao.map - -import android.content.Intent -import android.os.Bundle -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import com.kakao.vectormap.KakaoMap -import com.kakao.vectormap.KakaoMapReadyCallback -import com.kakao.vectormap.MapLifeCycleCallback -import com.kakao.vectormap.MapView -import java.lang.Exception - -class MapActivity : AppCompatActivity() { - private lateinit var kakaoMap: MapView - private lateinit var searchBox: TextView - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_map) - kakaoMap = findViewById(R.id.kakao_map) - searchBox = findViewById(R.id.search_box) - - kakaoMap.start(object : MapLifeCycleCallback() { - override fun onMapDestroy() { - } - - override fun onMapError(p0: Exception?) { - } - - }, object : KakaoMapReadyCallback(){ - override fun onMapReady(p0: KakaoMap) { - } - }) - - searchBox.setOnClickListener { - startActivity(Intent(this, SearchActivity::class.java)) - } - } - - override fun onResume() { - super.onResume() - kakaoMap.resume() - } - - override fun onPause() { - super.onPause() - kakaoMap.pause() - } - -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MapApplication.kt b/app/src/main/java/campus/tech/kakao/map/MapApplication.kt index ea0b1006..f86a2d1e 100644 --- a/app/src/main/java/campus/tech/kakao/map/MapApplication.kt +++ b/app/src/main/java/campus/tech/kakao/map/MapApplication.kt @@ -1,11 +1,25 @@ package campus.tech.kakao.map import android.app.Application +import campus.tech.kakao.map.data.repository.HistoryRepositoryImpl +import campus.tech.kakao.map.data.repository.LastLocationRepositoryImpl +import campus.tech.kakao.map.data.repository.ResultRepositoryImpl +import campus.tech.kakao.map.data.source.MapDbHelper +import campus.tech.kakao.map.data.source.RetrofitServiceClient import com.kakao.vectormap.KakaoMapSdk -class MapApplication: Application() { +class MapApplication : Application() { + lateinit var dbHelper: MapDbHelper + lateinit var resultRepositoryImpl: ResultRepositoryImpl + lateinit var historyRepositoryImpl: HistoryRepositoryImpl + lateinit var lastLocationRepositoryImpl: LastLocationRepositoryImpl + override fun onCreate() { super.onCreate() KakaoMapSdk.init(this, getString(R.string.kakao_api_key)) + dbHelper = MapDbHelper(this) + resultRepositoryImpl = ResultRepositoryImpl(RetrofitServiceClient.retrofitService) + historyRepositoryImpl = HistoryRepositoryImpl(dbHelper) + lastLocationRepositoryImpl = LastLocationRepositoryImpl(dbHelper) } } \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MapContract.kt b/app/src/main/java/campus/tech/kakao/map/MapContract.kt index dd2a18c2..0d3e8b43 100644 --- a/app/src/main/java/campus/tech/kakao/map/MapContract.kt +++ b/app/src/main/java/campus/tech/kakao/map/MapContract.kt @@ -3,11 +3,14 @@ package campus.tech.kakao.map import android.provider.BaseColumns object MapContract { - object MapEntry : BaseColumns { - const val TABLE_NAME = "map" + object MapEntry { const val TABLE_NAME_HISTORY = "history" + const val TABLE_NAME_LAST_LOCATION = "last_location" + const val COLUMN_NAME_ID = "id" const val COLUMN_NAME_NAME = "name" const val COLUMN_NAME_CATEGORY = "category" const val COLUMN_NAME_ADDRESS = "address" + const val COLUMN_NAME_X = "x" + const val COLUMN_NAME_Y = "y" } } \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MapDbHelper.kt b/app/src/main/java/campus/tech/kakao/map/MapDbHelper.kt deleted file mode 100644 index 03153561..00000000 --- a/app/src/main/java/campus/tech/kakao/map/MapDbHelper.kt +++ /dev/null @@ -1,68 +0,0 @@ -package campus.tech.kakao.map - -import android.content.ContentValues -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import android.provider.BaseColumns - -class MapDbHelper(mContext: Context) : - SQLiteOpenHelper(mContext, DATABASE_NAME, null, DATABASE_VERSION) { - override fun onCreate(db: SQLiteDatabase?) { - db?.execSQL(SQL_CREATE_ENTRIES) - db?.execSQL(SQL_CREATE_ENTRIES_HISTORY) -// initializeDb(db) - } - - override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { - db?.execSQL(SQL_DELETE_ENTRIES) - db?.execSQL(SQL_DELETE_ENTRIES_HISTORY) - onCreate(db) - } - - private fun initializeDb(db: SQLiteDatabase?) { - for (idx in 1..10) { - val exampleCafeValue = ContentValues() - exampleCafeValue.put(MapContract.MapEntry.COLUMN_NAME_NAME, "카페$idx") - exampleCafeValue.put(MapContract.MapEntry.COLUMN_NAME_CATEGORY, Location.CAFE) - exampleCafeValue.put(MapContract.MapEntry.COLUMN_NAME_ADDRESS, "서울 성동구 성수동 $idx") - db?.insert(MapContract.MapEntry.TABLE_NAME, null, exampleCafeValue) - - val examplePharValue = ContentValues() - examplePharValue.put(MapContract.MapEntry.COLUMN_NAME_NAME, "약국$idx") - examplePharValue.put(MapContract.MapEntry.COLUMN_NAME_CATEGORY, Location.PHARMACY) - examplePharValue.put(MapContract.MapEntry.COLUMN_NAME_ADDRESS, "서울 성동구 성수동 $idx") - db?.insert(MapContract.MapEntry.TABLE_NAME, null, examplePharValue) - } - } - - fun clearDb(db: SQLiteDatabase?) { - db?.execSQL(SQL_DELETE_ENTRIES) - db?.execSQL(SQL_CREATE_ENTRIES) - } - - companion object { - const val DATABASE_NAME = "map.db" - const val DATABASE_VERSION = 1 - - private const val SQL_CREATE_ENTRIES = """ - CREATE TABLE ${MapContract.MapEntry.TABLE_NAME} ( - ${BaseColumns._ID} INTEGER PRIMARY KEY AUTOINCREMENT, - ${MapContract.MapEntry.COLUMN_NAME_NAME} TEXT, - ${MapContract.MapEntry.COLUMN_NAME_CATEGORY} TEXT, - ${MapContract.MapEntry.COLUMN_NAME_ADDRESS} TEXT); - """ - - private const val SQL_DELETE_ENTRIES = - "DROP TABLE IF EXISTS ${MapContract.MapEntry.TABLE_NAME}" - - private const val SQL_CREATE_ENTRIES_HISTORY = """ - CREATE TABLE ${MapContract.MapEntry.TABLE_NAME_HISTORY} ( - ${BaseColumns._ID} INTEGER PRIMARY KEY AUTOINCREMENT, - ${MapContract.MapEntry.COLUMN_NAME_NAME} TEXT); - """ - - private const val SQL_DELETE_ENTRIES_HISTORY = - "DROP TABLE IF EXISTS ${MapContract.MapEntry.TABLE_NAME_HISTORY}" - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MapModel.kt b/app/src/main/java/campus/tech/kakao/map/MapModel.kt deleted file mode 100644 index e1b24d5a..00000000 --- a/app/src/main/java/campus/tech/kakao/map/MapModel.kt +++ /dev/null @@ -1,203 +0,0 @@ -package campus.tech.kakao.map - -import android.content.ContentValues -import android.content.Context -import android.database.Cursor -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class MapModel(dbHelper: MapDbHelper) { - private val helper: MapDbHelper = dbHelper - private val retrofit: RetrofitService = RetrofitServiceClient.getRetrofit("https://dapi.kakao.com/") - - private val _searchResult = MutableLiveData(getAllLocation()) - val searchResult: LiveData> = _searchResult - private val _searchHistory = MutableLiveData(getAllHistory()) - val searchHistory: LiveData> = _searchHistory - - fun searchByKeywordFromServer(keyword: String, isExactMatch: Boolean) { - clearDb() - request(keyword) - } - - fun insertLocation(location: Location) { - val writableDb = helper.writableDatabase - val content = ContentValues() - content.put(MapContract.MapEntry.COLUMN_NAME_NAME, location.name) - content.put(MapContract.MapEntry.COLUMN_NAME_CATEGORY, location.category) - content.put(MapContract.MapEntry.COLUMN_NAME_ADDRESS, location.address) - - writableDb.insert(MapContract.MapEntry.TABLE_NAME, null, content) - } - - fun getSearchedLocation(locName: String, isExactMatch: Boolean): List { - val readableDb = helper.readableDatabase - - val selection = "${MapContract.MapEntry.COLUMN_NAME_NAME} LIKE ?" - val selectionArgs = arrayOf("%${locName}%") - val cursor = readableDb.query( - MapContract.MapEntry.TABLE_NAME, - null, - selection, - selectionArgs, - null, - null, - null - ) - return getLocationResult(cursor) - } - - fun getAllLocation(): List { - val readableDb = helper.readableDatabase - val cursor = readableDb.query( - MapContract.MapEntry.TABLE_NAME, - null, - null, - null, - null, - null, - null - ) - return getLocationResult(cursor) - } - - private fun getLocationResult(cursor: Cursor): List { - val res = mutableListOf() - while (cursor.moveToNext()) { - res.add(getLocation(cursor)) - } - cursor.close() - return res - } - - private fun getLocation(cursor: Cursor): Location { - val name = - cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_NAME)) - val category = - cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_CATEGORY)) - val address = - cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_ADDRESS)) - - return Location(name, category, address) - } - - private fun getLocation(document: Document): Location { - val name = document.placeName - val category = - if (document.categoryGroupName != "") { - document.categoryGroupName - } else { - Location.NORMAL - } - - val address = - if (document.roadAddressName != "") { - document.roadAddressName - } else { - document.addressName - } - - - return Location(name, category, address) - } - - fun insertHistory(locName: String) { - - if (isHistoryExist(locName)) - deleteHistory(locName) - val writeableDb = helper.writableDatabase - val content = ContentValues() - content.put(MapContract.MapEntry.COLUMN_NAME_NAME, locName) - writeableDb.insert(MapContract.MapEntry.TABLE_NAME_HISTORY, null, content) - } - - private fun isHistoryExist(locName: String): Boolean { - val readableDb = helper.readableDatabase - val selection = "${MapContract.MapEntry.COLUMN_NAME_NAME} = ?" - val selectionArgs = arrayOf(locName) - val cursor = readableDb.query( - MapContract.MapEntry.TABLE_NAME_HISTORY, - null, - selection, - selectionArgs, - null, - null, - null - ) - val isExist: Boolean = cursor.moveToNext() - cursor.close() - return isExist - } - - fun deleteHistory(locName: String) { - val writeableDb = helper.writableDatabase - val selection = "${MapContract.MapEntry.COLUMN_NAME_NAME} = ?" - val selectionArgs = arrayOf(locName) - - writeableDb.delete(MapContract.MapEntry.TABLE_NAME_HISTORY, selection, selectionArgs) - } - - fun getAllHistory(): List { - val readableDb = helper.readableDatabase - val cursor = readableDb.query( - MapContract.MapEntry.TABLE_NAME_HISTORY, - null, - null, - null, - null, - null, - null - ) - - val res = mutableListOf() - while (cursor.moveToNext()) { - res.add(cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_NAME))) - } - cursor.close() - return res - } - - private fun request(keyword: String, page: Int = 1) { - val authorization = "KakaoAK ${BuildConfig.KAKAO_REST_API_KEY}" - retrofit.requestLocationByKeyword(authorization, keyword, page = page).enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - val body = response.body() - body?.let { - updateDb(body, page) - } - } - _searchResult.value = getAllLocation() - } - - override fun onFailure(call: Call, response: Throwable) { - Log.d("Model", "Fail") - } - }) - } - - private fun clearDb() { - helper.clearDb(helper.writableDatabase) - } - - private fun updateDb(serverResult: ServerResult, page: Int) { - val res = mutableListOf() - serverResult.docList.forEach { document -> - val location = getLocation(document) - insertLocation(location) - } - if (!serverResult.meta.isEnd) { - requestNextPage(serverResult, page) - } - } - - private fun requestNextPage(serverResult: ServerResult, page: Int) { - val keyword = serverResult.meta.sameName.keyword - val nextPage = page + 1 - request(keyword, nextPage) - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MapViewModel.kt b/app/src/main/java/campus/tech/kakao/map/MapViewModel.kt deleted file mode 100644 index 6555379f..00000000 --- a/app/src/main/java/campus/tech/kakao/map/MapViewModel.kt +++ /dev/null @@ -1,67 +0,0 @@ -package campus.tech.kakao.map - -import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModel - -class MapViewModel(dbHelper: MapDbHelper) : ViewModel() { - private val model: MapModel = MapModel(dbHelper) - private val _searchResult = MutableLiveData>() - val searchResult: LiveData> = _searchResult - private val _searchHistory = MutableLiveData>() - val searchHistory: LiveData> = _searchHistory - - private val resultObserver = Observer> { - _searchResult.value = it - } - private val historyObserver = Observer> { - _searchHistory.value = it - } - - init { - observeData() - } - - override fun onCleared() { - super.onCleared() - model.searchResult.removeObserver(resultObserver) - model.searchHistory.removeObserver(historyObserver) - } - - fun insertLocation(location: Location) { - model.insertLocation(location) - } - - fun searchLocation(locName: String, isExactMatch: Boolean) { - _searchResult.value = model.getSearchedLocation(locName, isExactMatch) - } - - fun getAllLocation(): List { - return model.getAllLocation() - } - - fun deleteHistory(historyName: String) { - model.deleteHistory(historyName) - _searchHistory.value = model.getAllHistory() - } - - fun insertHistory(historyName: String) { - model.insertHistory(historyName) - _searchHistory.value = model.getAllHistory() - } - - fun getAllHistory(): List { - return model.getAllHistory() - } - - fun searchByKeywordFromServer(keyword: String, isExactMatch: Boolean) { - model.searchByKeywordFromServer(keyword, isExactMatch) - } - - private fun observeData() { - model.searchResult.observeForever(resultObserver) - model.searchHistory.observeForever(historyObserver) - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/RetrofitServiceClient.kt b/app/src/main/java/campus/tech/kakao/map/RetrofitServiceClient.kt deleted file mode 100644 index 97ee81a3..00000000 --- a/app/src/main/java/campus/tech/kakao/map/RetrofitServiceClient.kt +++ /dev/null @@ -1,17 +0,0 @@ -package campus.tech.kakao.map - -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory - -object RetrofitServiceClient { - private lateinit var retrofitService: RetrofitService - - fun getRetrofit(baseUrl: String): RetrofitService { - retrofitService = Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(RetrofitService::class.java) - return retrofitService - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/ServerResult.kt b/app/src/main/java/campus/tech/kakao/map/data/ServerResult.kt similarity index 97% rename from app/src/main/java/campus/tech/kakao/map/ServerResult.kt rename to app/src/main/java/campus/tech/kakao/map/data/ServerResult.kt index 056605f5..d1d98e19 100644 --- a/app/src/main/java/campus/tech/kakao/map/ServerResult.kt +++ b/app/src/main/java/campus/tech/kakao/map/data/ServerResult.kt @@ -1,4 +1,4 @@ -package campus.tech.kakao.map +package campus.tech.kakao.map.data import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/campus/tech/kakao/map/data/repository/HistoryRepositoryImpl.kt b/app/src/main/java/campus/tech/kakao/map/data/repository/HistoryRepositoryImpl.kt new file mode 100644 index 00000000..5919cd2d --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/repository/HistoryRepositoryImpl.kt @@ -0,0 +1,98 @@ +package campus.tech.kakao.map.data.repository + +import android.content.ContentValues +import android.database.Cursor +import campus.tech.kakao.map.MapContract +import campus.tech.kakao.map.data.source.MapDbHelper +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.domain.repository.HistoryRepository + +class HistoryRepositoryImpl( + private val helper: MapDbHelper +) : HistoryRepository { + override fun insertHistory(newHistory: Location) { + if (isHistoryExist(newHistory)) + deleteHistory(newHistory) + val writeableDb = helper.writableDatabase + val content = historyToContent(newHistory) + + writeableDb.insert(MapContract.MapEntry.TABLE_NAME_HISTORY, null, content) + } + + override fun deleteHistory(oldHistory: Location) { + val writeableDb = helper.writableDatabase + val selection = "${MapContract.MapEntry.COLUMN_NAME_ID} = ?" + val selectionArgs = arrayOf(oldHistory.id) + + writeableDb.delete(MapContract.MapEntry.TABLE_NAME_HISTORY, selection, selectionArgs) + } + + override fun getAllHistory(): List { + val readableDb = helper.readableDatabase + val cursor = readableDb.query( + MapContract.MapEntry.TABLE_NAME_HISTORY, + null, + null, + null, + null, + null, + null + ) + + val res = mutableListOf() + while (cursor.moveToNext()) { + res.add(cursorToHistory(cursor)) + } + cursor.close() + return res + } + + private fun isHistoryExist(newHistory: Location): Boolean { + val readableDb = helper.readableDatabase + val selection = "${MapContract.MapEntry.COLUMN_NAME_ID} = ?" + val selectionArgs = arrayOf(newHistory.id) + val cursor = readableDb.query( + MapContract.MapEntry.TABLE_NAME_HISTORY, + null, + selection, + selectionArgs, + null, + null, + null + ) + val isExist: Boolean = cursor.moveToNext() + cursor.close() + return isExist + } + + private fun historyToContent(location: Location): ContentValues { + val content = ContentValues() + content.put(MapContract.MapEntry.COLUMN_NAME_ID, location.id) + content.put(MapContract.MapEntry.COLUMN_NAME_NAME, location.name) + content.put(MapContract.MapEntry.COLUMN_NAME_CATEGORY, location.category) + content.put(MapContract.MapEntry.COLUMN_NAME_ADDRESS, location.address) + content.put(MapContract.MapEntry.COLUMN_NAME_X, location.x) + content.put(MapContract.MapEntry.COLUMN_NAME_Y, location.y) + + return content + } + + private fun cursorToHistory(cursor: Cursor): Location { + val id = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_ID)) + val name = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_NAME)) + val category = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_CATEGORY)) + val address = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_ADDRESS)) + val x = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_X)) + .toDouble() + val y = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_Y)) + .toDouble() + + return Location(id, name, category, address, x, y) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/data/repository/LastLocationRepositoryImpl.kt b/app/src/main/java/campus/tech/kakao/map/data/repository/LastLocationRepositoryImpl.kt new file mode 100644 index 00000000..31de396b --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/repository/LastLocationRepositoryImpl.kt @@ -0,0 +1,73 @@ +package campus.tech.kakao.map.data.repository + +import android.content.ContentValues +import android.database.Cursor +import android.util.Log +import campus.tech.kakao.map.MapContract +import campus.tech.kakao.map.data.source.MapDbHelper +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.domain.repository.LastLocationRepository + +class LastLocationRepositoryImpl( + private val helper: MapDbHelper +) : LastLocationRepository { + override fun insertLastLocation(location: Location) { + val writeableDb = helper.writableDatabase + val content = locationToContent(location) + clearLastLocation() + writeableDb.insert(MapContract.MapEntry.TABLE_NAME_LAST_LOCATION, null, content) + } + + override fun clearLastLocation() { + val writeableDb = helper.writableDatabase + helper.clearLastLocation(writeableDb) + } + + override fun getLastLocation(): Location? { + val readableDb = helper.readableDatabase + val cursor = readableDb.query( + MapContract.MapEntry.TABLE_NAME_LAST_LOCATION, + null, + null, + null, + null, + null, + null + ) + return if (cursor.moveToNext()) + cursorToLocation(cursor) + else + null + } + + private fun locationToContent(location: Location): ContentValues { + val content = ContentValues() + content.put(MapContract.MapEntry.COLUMN_NAME_ID, location.id) + content.put(MapContract.MapEntry.COLUMN_NAME_NAME, location.name) + content.put(MapContract.MapEntry.COLUMN_NAME_CATEGORY, location.category) + content.put(MapContract.MapEntry.COLUMN_NAME_ADDRESS, location.address) + content.put(MapContract.MapEntry.COLUMN_NAME_X, location.x) + content.put(MapContract.MapEntry.COLUMN_NAME_Y, location.y) + + return content + } + + private fun cursorToLocation(cursor: Cursor): Location { + val id = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_ID)) + val name = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_NAME)) + val category = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_CATEGORY)) + val address = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_ADDRESS)) + val x = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_X)) + .toDouble() + val y = + cursor.getString(cursor.getColumnIndexOrThrow(MapContract.MapEntry.COLUMN_NAME_Y)) + .toDouble() + + return Location(id, name, category, address, x, y) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/data/repository/ResultRepositoryImpl.kt b/app/src/main/java/campus/tech/kakao/map/data/repository/ResultRepositoryImpl.kt new file mode 100644 index 00000000..e0c7e824 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/repository/ResultRepositoryImpl.kt @@ -0,0 +1,78 @@ +package campus.tech.kakao.map.data.repository + +import android.app.Application +import android.widget.Toast +import campus.tech.kakao.map.BuildConfig +import campus.tech.kakao.map.data.Document +import campus.tech.kakao.map.data.ServerResult +import campus.tech.kakao.map.data.source.RetrofitService +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.domain.repository.ResultRepository + +class ResultRepositoryImpl( + private val retrofit: RetrofitService +) : ResultRepository { + private val result = mutableListOf() + + override suspend fun search(keyword: String): List { + clearResult() + request(keyword) + return result + } + + override fun getAllResult(): List { + return result + } + + private suspend fun request(keyword: String, currPage: Int = 1, tryCount: Int = 0) { + if (keyword.isNotEmpty()) { + val authorization = "KakaoAK ${BuildConfig.KAKAO_REST_API_KEY}" + val response = retrofit.requestLocationByKeyword(authorization, keyword, currPage) + if (response.isSuccessful) { + val resBody = response.body() + resBody?.let { serverResult -> + serverResult.docList.forEach { document -> + result.add(documentToLocation(document)) + } + if (!serverResult.meta.isEnd) + requestNextPage(serverResult, currPage) + } + } else if (tryCount < 3) { + request(keyword, currPage, tryCount + 1) + } + } + } + + private fun clearResult() { + result.clear() + } + + private suspend fun requestNextPage(serverResult: ServerResult, page: Int) { + val keyword = serverResult.meta.sameName.keyword + val nextPage = page + 1 + request(keyword, nextPage, 0) + } + + private fun documentToLocation(document: Document): Location { + val id = document.id + val name = document.placeName + val category = + if (document.categoryGroupName != "") { + document.categoryGroupName + } else { + Location.NORMAL + } + + val address = + if (document.roadAddressName != "") { + document.roadAddressName + } else { + document.addressName + } + + val x = document.x.toDouble() + val y = document.y.toDouble() + + return Location(id, name, category, address, x, y) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/data/source/MapDbHelper.kt b/app/src/main/java/campus/tech/kakao/map/data/source/MapDbHelper.kt new file mode 100644 index 00000000..6639438c --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/source/MapDbHelper.kt @@ -0,0 +1,56 @@ +package campus.tech.kakao.map.data.source + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import campus.tech.kakao.map.MapContract + +class MapDbHelper(mContext: Context) : + SQLiteOpenHelper(mContext, DATABASE_NAME, null, DATABASE_VERSION) { + override fun onCreate(db: SQLiteDatabase?) { + db?.execSQL(SQL_CREATE_ENTRIES_HISTORY) + db?.execSQL(SQL_CREATE_ENTRIES_LAST_LOCATION) + } + + override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { + db?.execSQL(SQL_DELETE_ENTRIES_HISTORY) + db?.execSQL(SQL_DELETE_ENTRIES_LAST_LOCATION) + onCreate(db) + } + + fun clearLastLocation(db: SQLiteDatabase?) { + db?.execSQL(SQL_DELETE_ENTRIES_LAST_LOCATION) + db?.execSQL(SQL_CREATE_ENTRIES_LAST_LOCATION) + } + + companion object { + const val DATABASE_NAME = "map.db" + const val DATABASE_VERSION = 1 + + private const val SQL_CREATE_ENTRIES_HISTORY = """ + CREATE TABLE ${MapContract.MapEntry.TABLE_NAME_HISTORY} ( + ${MapContract.MapEntry.COLUMN_NAME_ID} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_NAME} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_CATEGORY} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_ADDRESS} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_X} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_Y} TEXT); + """ + + private const val SQL_DELETE_ENTRIES_HISTORY = + "DROP TABLE IF EXISTS ${MapContract.MapEntry.TABLE_NAME_HISTORY}" + + private const val SQL_CREATE_ENTRIES_LAST_LOCATION = """ + CREATE TABLE ${MapContract.MapEntry.TABLE_NAME_LAST_LOCATION} ( + ${MapContract.MapEntry.COLUMN_NAME_ID} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_NAME} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_CATEGORY} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_ADDRESS} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_X} TEXT, + ${MapContract.MapEntry.COLUMN_NAME_Y} TEXT); + """ + + private const val SQL_DELETE_ENTRIES_LAST_LOCATION = + "DROP TABLE IF EXISTS ${MapContract.MapEntry.TABLE_NAME_LAST_LOCATION}" + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/RetrofitService.kt b/app/src/main/java/campus/tech/kakao/map/data/source/RetrofitService.kt similarity index 76% rename from app/src/main/java/campus/tech/kakao/map/RetrofitService.kt rename to app/src/main/java/campus/tech/kakao/map/data/source/RetrofitService.kt index b397852a..6829f239 100644 --- a/app/src/main/java/campus/tech/kakao/map/RetrofitService.kt +++ b/app/src/main/java/campus/tech/kakao/map/data/source/RetrofitService.kt @@ -1,6 +1,8 @@ -package campus.tech.kakao.map +package campus.tech.kakao.map.data.source +import campus.tech.kakao.map.data.ServerResult import retrofit2.Call +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.Query @@ -15,9 +17,9 @@ interface RetrofitService { ) : Call @GET("v2/local/search/keyword") - fun requestLocationByKeyword( + suspend fun requestLocationByKeyword( @Header("Authorization") authorization: String, @Query("query") keyword: String, @Query("page") page: Int = 1 - ) : Call + ) : Response } \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/data/source/RetrofitServiceClient.kt b/app/src/main/java/campus/tech/kakao/map/data/source/RetrofitServiceClient.kt new file mode 100644 index 00000000..18768a14 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/data/source/RetrofitServiceClient.kt @@ -0,0 +1,13 @@ +package campus.tech.kakao.map.data.source + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object RetrofitServiceClient { + val retrofitService: RetrofitService = + Retrofit.Builder() + .baseUrl("https://dapi.kakao.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(RetrofitService::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/domain/model/Location.kt b/app/src/main/java/campus/tech/kakao/map/domain/model/Location.kt new file mode 100644 index 00000000..99e6e976 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/domain/model/Location.kt @@ -0,0 +1,17 @@ +package campus.tech.kakao.map.domain.model + +import java.io.Serializable + +data class Location( + val id: String, + val name: String, + val category: String, + val address: String, + val x: Double, + val y: Double +) : Serializable { + companion object { + const val LOCATION: String = "LOCATION" + const val NORMAL: String = "일반" + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/domain/repository/HistoryRepository.kt b/app/src/main/java/campus/tech/kakao/map/domain/repository/HistoryRepository.kt new file mode 100644 index 00000000..a6ad2b2d --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/domain/repository/HistoryRepository.kt @@ -0,0 +1,9 @@ +package campus.tech.kakao.map.domain.repository + +import campus.tech.kakao.map.domain.model.Location + +interface HistoryRepository { + fun insertHistory(newHistory: Location) + fun deleteHistory(oldHistory: Location) + fun getAllHistory(): List +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/domain/repository/LastLocationRepository.kt b/app/src/main/java/campus/tech/kakao/map/domain/repository/LastLocationRepository.kt new file mode 100644 index 00000000..f06d77f4 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/domain/repository/LastLocationRepository.kt @@ -0,0 +1,9 @@ +package campus.tech.kakao.map.domain.repository + +import campus.tech.kakao.map.domain.model.Location + +interface LastLocationRepository { + fun insertLastLocation(location: Location) + fun clearLastLocation() + fun getLastLocation(): Location? +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/domain/repository/ResultRepository.kt b/app/src/main/java/campus/tech/kakao/map/domain/repository/ResultRepository.kt new file mode 100644 index 00000000..5a3fcf69 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/domain/repository/ResultRepository.kt @@ -0,0 +1,8 @@ +package campus.tech.kakao.map.domain.repository + +import campus.tech.kakao.map.domain.model.Location + +interface ResultRepository { + suspend fun search(keyword: String): List + fun getAllResult(): List +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/HistoryRecyclerAdapter.kt b/app/src/main/java/campus/tech/kakao/map/presentation/HistoryRecyclerAdapter.kt similarity index 70% rename from app/src/main/java/campus/tech/kakao/map/HistoryRecyclerAdapter.kt rename to app/src/main/java/campus/tech/kakao/map/presentation/HistoryRecyclerAdapter.kt index a0b135a3..007f7d45 100644 --- a/app/src/main/java/campus/tech/kakao/map/HistoryRecyclerAdapter.kt +++ b/app/src/main/java/campus/tech/kakao/map/presentation/HistoryRecyclerAdapter.kt @@ -1,19 +1,18 @@ -package campus.tech.kakao.map +package campus.tech.kakao.map.presentation -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageButton import android.widget.TextView -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder +import campus.tech.kakao.map.DatabaseListener +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.R class HistoryRecyclerAdapter( - var history: List, + var history: List, val layoutInflater: LayoutInflater, val databaseListener: DatabaseListener ) : RecyclerView.Adapter() { @@ -24,9 +23,14 @@ class HistoryRecyclerAdapter( init { clear.setOnClickListener { if (bindingAdapterPosition != RecyclerView.NO_POSITION) { - databaseListener.deleteHistory(name.text.toString()) + databaseListener.deleteHistory(history[bindingAdapterPosition]) } } + + itemView.setOnClickListener { + databaseListener.searchHistory(name.text.toString(), false) + databaseListener.insertHistory(history[bindingAdapterPosition]) + } } } @@ -36,7 +40,7 @@ class HistoryRecyclerAdapter( } override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) { - holder.name.text = history[position] + holder.name.text = history[position].name } override fun getItemCount(): Int { diff --git a/app/src/main/java/campus/tech/kakao/map/presentation/MapActivity.kt b/app/src/main/java/campus/tech/kakao/map/presentation/MapActivity.kt new file mode 100644 index 00000000..43553795 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/presentation/MapActivity.kt @@ -0,0 +1,136 @@ +package campus.tech.kakao.map.presentation + +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.R +import campus.tech.kakao.map.data.repository.HistoryRepositoryImpl +import campus.tech.kakao.map.data.repository.LastLocationRepositoryImpl +import campus.tech.kakao.map.data.repository.ResultRepositoryImpl +import campus.tech.kakao.map.data.source.MapDbHelper +import campus.tech.kakao.map.data.source.RetrofitServiceClient +import campus.tech.kakao.map.domain.repository.HistoryRepository +import campus.tech.kakao.map.domain.repository.ResultRepository +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.LabelLayerOptions +import com.kakao.vectormap.label.LabelManager +import com.kakao.vectormap.label.LabelOptions +import com.kakao.vectormap.label.LabelStyle +import com.kakao.vectormap.label.LabelStyles + + +class MapActivity : AppCompatActivity() { + private val TAG = "KAKAOMAP" + private lateinit var kakaoMapView: MapView + private lateinit var kakaoMap: KakaoMap + private lateinit var searchBox: TextView + private lateinit var infoSheetLayout: LinearLayout + private lateinit var infoSheetName: TextView + private lateinit var infoSheetAddress: TextView + private lateinit var errorLayout: ConstraintLayout + private lateinit var errorCode: TextView + + private lateinit var viewModel: MapViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_map) + kakaoMapView = findViewById(R.id.kakao_map_view) + searchBox = findViewById(R.id.search_box) + infoSheetLayout = findViewById(R.id.info_sheet) + infoSheetName = findViewById(R.id.info_sheet_name) + infoSheetAddress = findViewById(R.id.info_sheet_address) + errorLayout = findViewById(R.id.error_layout) + errorCode = findViewById(R.id.error_code) + + val dbhelper = MapDbHelper(this) + val resultRepo = ResultRepositoryImpl(RetrofitServiceClient.retrofitService) + val historyRepo = HistoryRepositoryImpl(dbhelper) + val lastRepo = LastLocationRepositoryImpl(dbhelper) + viewModel = MapViewModel(dbhelper, resultRepo, historyRepo, lastRepo) + + kakaoMapView.start(object : MapLifeCycleCallback() { + override fun onMapDestroy() { + // TODO: 필요시 구현 예정 + } + + override fun onMapError(exception: Exception?) { + hideInfo(exception) + } + + }, object : KakaoMapReadyCallback() { + override fun onMapReady(kakaoMap: KakaoMap) { + this@MapActivity.kakaoMap = kakaoMap + val target = viewModel.getLastLocation() + if (::kakaoMap.isInitialized && target != null) { + moveToTargetLocation(kakaoMap, target) + showInfo(target) + } + Log.d(TAG, "onMapReady") + } + }) + + searchBox.setOnClickListener { + startActivity(Intent(this, SearchActivity::class.java)) + } + } + + override fun onResume() { + super.onResume() + kakaoMapView.resume() + val target = viewModel.getLastLocation() + if (::kakaoMap.isInitialized && target != null) { + moveToTargetLocation(kakaoMap, target) + showInfo(target) + } + Log.d(TAG, "onResume") + } + + override fun onPause() { + super.onPause() + kakaoMapView.pause() + Log.d("KAKAOMAP", "onPause") + } + + private fun moveToTargetLocation(kakaoMap: KakaoMap, target: Location) { + val cameraUpdate = CameraUpdateFactory.newCenterPosition(LatLng.from(target.y, target.x)) + kakaoMap.moveCamera(cameraUpdate) + val labelManager = kakaoMap.labelManager + labelManager?.let { setPin(labelManager, target) } + } + + private fun showInfo(target: Location) { + infoSheetLayout.isVisible = true + infoSheetName.text = target.name + infoSheetAddress.text = target.address + } + + private fun hideInfo(exception: Exception?) { + errorCode.text = exception?.message + errorLayout.isVisible = true + kakaoMapView.isVisible = false + searchBox.isVisible = false + } + + private fun setPin(labelManager: LabelManager, target: Location) { + labelManager.removeAllLabelLayer() + val style = labelManager + .addLabelStyles(LabelStyles.from(LabelStyle.from(R.drawable.location_label).setTextStyles(30, Color.BLACK))) + labelManager.layer + ?.addLabel(LabelOptions.from(LatLng.from(target.y, target.x)).setStyles(style).setTexts(target.name)) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/presentation/MapViewModel.kt b/app/src/main/java/campus/tech/kakao/map/presentation/MapViewModel.kt new file mode 100644 index 00000000..a0f9e6cb --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/presentation/MapViewModel.kt @@ -0,0 +1,82 @@ +package campus.tech.kakao.map.presentation + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import campus.tech.kakao.map.MapApplication +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.data.source.MapDbHelper +import campus.tech.kakao.map.domain.repository.HistoryRepository +import campus.tech.kakao.map.domain.repository.LastLocationRepository +import campus.tech.kakao.map.domain.repository.ResultRepository +import kotlinx.coroutines.launch + +class MapViewModel( + dbHelper: MapDbHelper, + private val resultRepository: ResultRepository, + private val historyRepository: HistoryRepository, + private val lastLocationRepository: LastLocationRepository +) : ViewModel() { + private val _searchResult = MutableLiveData>() + val searchResult: LiveData> = _searchResult + private val _searchHistory = MutableLiveData>() + val searchHistory: LiveData> = _searchHistory + private val _lastLocation = MutableLiveData() + val lastLocation: LiveData = _lastLocation + + fun searchKeyword(keyword: String) { + viewModelScope.launch { + resultRepository.search(keyword) + _searchResult.value = resultRepository.getAllResult() + } + } + + fun getAllResult(): List { + return resultRepository.getAllResult() + } + + fun insertHistory(newHistory: Location) { + historyRepository.insertHistory(newHistory) + _searchHistory.value = historyRepository.getAllHistory() + } + + fun deleteHistory(oldHistory: Location) { + historyRepository.deleteHistory(oldHistory) + _searchHistory.value = historyRepository.getAllHistory() + } + + fun getAllHistory(): List { + return historyRepository.getAllHistory() + } + + fun insertLastLocation(location: Location) { + lastLocationRepository.insertLastLocation(location) + _lastLocation.value = lastLocationRepository.getLastLocation() + Log.d("ViewModel", _lastLocation.value.toString()) + } + + fun getLastLocation(): Location? { + _lastLocation.value = lastLocationRepository.getLastLocation() + return lastLocationRepository.getLastLocation() + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = (this[APPLICATION_KEY] as MapApplication) + MapViewModel( + application.dbHelper, + application.resultRepositoryImpl, + application.historyRepositoryImpl, + application.lastLocationRepositoryImpl + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/ResultRecyclerAdapter.kt b/app/src/main/java/campus/tech/kakao/map/presentation/ResultRecyclerAdapter.kt similarity index 77% rename from app/src/main/java/campus/tech/kakao/map/ResultRecyclerAdapter.kt rename to app/src/main/java/campus/tech/kakao/map/presentation/ResultRecyclerAdapter.kt index c09a1221..1f88ee05 100644 --- a/app/src/main/java/campus/tech/kakao/map/ResultRecyclerAdapter.kt +++ b/app/src/main/java/campus/tech/kakao/map/presentation/ResultRecyclerAdapter.kt @@ -1,12 +1,14 @@ -package campus.tech.kakao.map +package campus.tech.kakao.map.presentation -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder +import campus.tech.kakao.map.DatabaseListener +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.R class ResultRecyclerAdapter( var searchResult: List, @@ -21,7 +23,10 @@ class ResultRecyclerAdapter( init { itemView.setOnClickListener { if (bindingAdapterPosition != RecyclerView.NO_POSITION) { - databaseListener.insertHistory(name.text.toString()) + val clickedResult = searchResult[bindingAdapterPosition] + databaseListener.insertHistory(clickedResult) + databaseListener.insertLastLocation(clickedResult) + databaseListener.showMap(clickedResult) } } } diff --git a/app/src/main/java/campus/tech/kakao/map/SearchActivity.kt b/app/src/main/java/campus/tech/kakao/map/presentation/SearchActivity.kt similarity index 52% rename from app/src/main/java/campus/tech/kakao/map/SearchActivity.kt rename to app/src/main/java/campus/tech/kakao/map/presentation/SearchActivity.kt index d9e09994..6b1a4b54 100644 --- a/app/src/main/java/campus/tech/kakao/map/SearchActivity.kt +++ b/app/src/main/java/campus/tech/kakao/map/presentation/SearchActivity.kt @@ -1,19 +1,29 @@ -package campus.tech.kakao.map +package campus.tech.kakao.map.presentation +import android.content.Intent import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.widget.EditText import android.widget.ImageButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible -import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import campus.tech.kakao.map.DatabaseListener +import campus.tech.kakao.map.domain.model.Location +import campus.tech.kakao.map.R +import campus.tech.kakao.map.data.repository.HistoryRepositoryImpl +import campus.tech.kakao.map.data.repository.LastLocationRepositoryImpl +import campus.tech.kakao.map.data.repository.ResultRepositoryImpl +import campus.tech.kakao.map.data.source.MapDbHelper +import campus.tech.kakao.map.data.source.RetrofitServiceClient class SearchActivity : AppCompatActivity(), DatabaseListener { +// private val viewModel: MapViewModel by viewModels() private lateinit var viewModel: MapViewModel - private lateinit var searchBox: EditText private lateinit var searchHistoryView: RecyclerView private lateinit var searchResultView: RecyclerView @@ -22,27 +32,27 @@ class SearchActivity : AppCompatActivity(), DatabaseListener { private lateinit var searchResultAdapter: ResultRecyclerAdapter private lateinit var searchHistoryAdapter: HistoryRecyclerAdapter - + private val searchBoxWatcher = getSearchBoxWatcher() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_search) val dbHelper = MapDbHelper(this) - viewModel = MapViewModel(dbHelper) + val retrofit = RetrofitServiceClient.retrofitService + viewModel = MapViewModel( + dbHelper, + ResultRepositoryImpl(retrofit) , + HistoryRepositoryImpl(dbHelper), + LastLocationRepositoryImpl(dbHelper) + ) searchBox = findViewById(R.id.search_box) searchHistoryView = findViewById(R.id.search_history) searchResultView = findViewById(R.id.search_result) message = findViewById(R.id.message) clear = findViewById(R.id.clear) - searchBox.doAfterTextChanged { text -> - if (text.isNullOrEmpty()) { - hideResult() - } else { - search(text.toString(), false) - } - } + searchBox.addTextChangedListener(searchBoxWatcher) clear.setOnClickListener { searchBox.text.clear() @@ -53,12 +63,31 @@ class SearchActivity : AppCompatActivity(), DatabaseListener { observeData() } - override fun deleteHistory(historyName: String) { - viewModel.deleteHistory(historyName) + override fun deleteHistory(oldHistory: Location) { + viewModel.deleteHistory(oldHistory) + } + + override fun insertHistory(newHistory: Location) { + viewModel.insertHistory(newHistory) + } + + override fun showMap(newHistory: Location) { + finish() + } + + override fun insertLastLocation(location: Location) { + viewModel.insertLastLocation(location) } - override fun insertHistory(historyName: String) { - viewModel.insertHistory(historyName) + override fun searchHistory(locName: String, isExactMatch: Boolean) { + searchBox.removeTextChangedListener(searchBoxWatcher) + searchBox.setText(locName) + searchBox.addTextChangedListener(searchBoxWatcher) + viewModel.searchKeyword(locName) + } + + private fun search(locName: String) { + viewModel.searchKeyword(locName) } private fun hideResult() { @@ -69,13 +98,10 @@ class SearchActivity : AppCompatActivity(), DatabaseListener { searchResultView.isVisible = true message.isVisible = false } - private fun search(locName: String, isExactMatch: Boolean) { - viewModel.searchByKeywordFromServer(locName, isExactMatch) - } private fun initSearchResultView() { searchResultAdapter = - ResultRecyclerAdapter(viewModel.searchResult.value!!, layoutInflater, this) + ResultRecyclerAdapter(viewModel.getAllResult(), layoutInflater, this) searchResultView.adapter = searchResultAdapter searchResultView.layoutManager = LinearLayoutManager(this@SearchActivity, LinearLayoutManager.VERTICAL, false) @@ -96,7 +122,7 @@ class SearchActivity : AppCompatActivity(), DatabaseListener { }) viewModel.searchResult.observe(this, Observer { searchResultAdapter.searchResult = it - if (it.isNotEmpty() && (searchBox.text.toString() != "")) { + if (it.isNotEmpty() && searchBox.text.isNotEmpty()) { showResult() } else { hideResult() @@ -104,4 +130,24 @@ class SearchActivity : AppCompatActivity(), DatabaseListener { searchResultAdapter.refreshList() }) } + + private fun getSearchBoxWatcher(): TextWatcher { + return object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + // TODO: 필요시 구현 예정 + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + // TODO: 필요시 구현 예정 + } + + override fun afterTextChanged(s: Editable?) { + if (s.isNullOrEmpty()) { + hideResult() + } else { + search(s.toString()) + } + } + } + } } diff --git a/app/src/main/res/drawable-hdpi/location_label.png b/app/src/main/res/drawable-hdpi/location_label.png new file mode 100644 index 00000000..d45cf1d4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/location_label.png differ diff --git a/app/src/main/res/drawable-mdpi/location_label.png b/app/src/main/res/drawable-mdpi/location_label.png new file mode 100644 index 00000000..4c9bff81 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/location_label.png differ diff --git a/app/src/main/res/drawable-xhdpi/location_label.png b/app/src/main/res/drawable-xhdpi/location_label.png new file mode 100644 index 00000000..7ab21db1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/location_label.png differ diff --git a/app/src/main/res/drawable-xxhdpi/location_label.png b/app/src/main/res/drawable-xxhdpi/location_label.png new file mode 100644 index 00000000..77b37906 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/location_label.png differ diff --git a/app/src/main/res/drawable/background_bottom_sheet.xml b/app/src/main/res/drawable/background_bottom_sheet.xml new file mode 100644 index 00000000..ec121cbc --- /dev/null +++ b/app/src/main/res/drawable/background_bottom_sheet.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/failed.png b/app/src/main/res/drawable/failed.png new file mode 100644 index 00000000..83e9b7c7 Binary files /dev/null and b/app/src/main/res/drawable/failed.png differ diff --git a/app/src/main/res/drawable/refresh_24.xml b/app/src/main/res/drawable/refresh_24.xml new file mode 100644 index 00000000..86504d0e --- /dev/null +++ b/app/src/main/res/drawable/refresh_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_map.xml b/app/src/main/res/layout/activity_map.xml index d7eefca7..4bbec1e5 100644 --- a/app/src/main/res/layout/activity_map.xml +++ b/app/src/main/res/layout/activity_map.xml @@ -1,25 +1,104 @@ - - + - + + + + + + + + - \ No newline at end of file + android:layout_height="match_parent"> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index e6d47e36..c3f4d956 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -5,7 +5,7 @@ android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".SearchActivity"> + tools:context=".presentation.SearchActivity"> 검색 결과가 없습니다. 검색어를 입력해 주세요. 삭제 + 지도 인증을 실패했습니다.\n다시 시도해 주세요. \ No newline at end of file