diff --git "a/3\354\243\274\354\260\250\352\263\274\354\240\234\354\213\244\355\226\211\354\230\201\354\203\201.webm" "b/3\354\243\274\354\260\250\352\263\274\354\240\234\354\213\244\355\226\211\354\230\201\354\203\201.webm" new file mode 100644 index 00000000..9e7654f5 Binary files /dev/null and "b/3\354\243\274\354\260\250\352\263\274\354\240\234\354\213\244\355\226\211\354\230\201\354\203\201.webm" differ diff --git a/README.md b/README.md index e0f925ff..75510c2e 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ # android-map-search +## ๐Ÿ™‹โ€โ™€๏ธ ๊ฐœ์š” +์ด App์€ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๊ณ , ์„ ํƒ๋œ ํ•ญ๋ชฉ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ์ €์žฅ๋œ ๊ฒ€์ƒ‰์–ด ๋ชฉ๋ก์€ ์•ฑ์„ ์žฌ์‹คํ–‰ํ•ด๋„ ์œ ์ง€๋œ๋‹ค. ๊ฒ€์ƒ‰ ๋ฐ์ดํ„ฐ๋Š” ์นด์นด์˜ค๋กœ์ปฌ API๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์ง€๋„๋Š” ์นด์นด์˜ค์ง€๋„ SDK๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. + +## โœจ ์ฃผ์š”๊ธฐ๋Šฅ +>**STEP1_๊ฒ€์ƒ‰ํ™”๋ฉด** +- ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ 15๊ฐœ ์ด์ƒ ํ‘œ์‹œ๋œ๋‹ค + - ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ_๋ฐ์ดํ„ฐ๋Š” ์นด์นด์˜ค๋กœ์ปฌ API๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค + +>**STEP2_์ง€๋„ํ™”๋ฉด** +- ์•ฑ ์ฒ˜์Œ ์‹คํ–‰์‹œ ๊ฒ€์ƒ‰ํ™”๋ฉด X, ์ง€๋„ํ™”๋ฉด์ด ํ‘œ์‹œ๋˜๋„๋ก ํ•œ๋‹ค + - ์ง€๋„ํ™”๋ฉด์€ ์นด์นด์˜ค์ง€๋„ SDK๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค +- `๊ฒ€์ƒ‰์ฐฝ ํด๋ฆญ`์‹œ ๊ฒ€์ƒ‰ํ™”๋ฉด์ด์œผ๋กœ ์ด๋™ํ•˜๋„๋ก ํ•œ๋‹ค +- ๊ฒ€์ƒ‰ํ™”๋ฉด์—์„œ `๋‹จ๋ง๊ธฐ_๋’ค๋กœ๊ฐ€๊ธฐ`๋ฅผ ํ•˜๋ฉด ์ง€๋„ํ™”๋ฉด์œผ๋กœ ๋Œ์•„์˜จ๋‹ค + + +## ๐Ÿ“ฑ ์‹คํ–‰ํ™”๋ฉด +1. ์•ฑ ์ฒ˜์Œ ์‹คํ–‰์‹œ, ์ง€๋„ํ™”๋ฉด ํ‘œ์‹œ + + +![์ง€๋„ํ™”๋ฉด](https://github.com/arieum/android-map-search/blob/arieum_step2/%EC%B2%AB%ED%99%94%EB%A9%B4_%EC%A7%80%EB%8F%84%ED%99%94%EB%A9%B4.png) + +2. ์นด์นด์˜ค๋กœ์ปฌ API๋กœ๋ถ€ํ„ฐ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + + +![๊ฒ€์ƒ‰๊ฒฐ๊ณผํ™”๋ฉด](https://github.com/arieum/android-map-search/blob/arieum_step2/%EA%B2%B0%EA%B3%BC%ED%99%94%EB%A9%B4fromapi.png) + +3. ์ „์ฒด์ ์ธ ์•ฑ ์‹คํ–‰์˜์ƒ + ๊ฒ€์ƒ‰ํ™”๋ฉด์—์„œ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ˆ„๋ฅด๋ฉด ๋‹ค์‹œ ์ง€๋„ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๊ธฐ +![3์ฃผ์ฐจ๊ณผ์ œ์‹คํ–‰์˜์ƒ](https://github.com/arieum/android-map-search/blob/arieum_step2/3%EC%A3%BC%EC%B0%A8%EA%B3%BC%EC%A0%9C%EC%8B%A4%ED%96%89%EC%98%81%EC%83%81.webm) + +## โš™๏ธ ์‚ฌ์šฉ๋œ API ๋ฐ SDK +- ์นด์นด์˜ค๋กœ์ปฌ API_Document โ˜ž +- ์นด์นด์˜ค์ง€๋„ SDK_Document โ˜ž + +![PrayingCatGIF](https://github.com/arieum/android-map-search/assets/143606293/a0bc3779-a19a-4e15-aae0-d7a475662aea) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e25e2553..50bd9053 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -15,6 +17,19 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + val kakaoApiKey = getApiKey("KAKAO_API_KEY") + val kakaoRestApiKey = getApiKey("KAKAO_REST_API_KEY") + + buildConfigField("String", "KAKAO_API_KEY", kakaoApiKey) + buildConfigField("String", "KAKAO_REST_API_KEY", kakaoRestApiKey) + + ndk { + abiFilters.add("arm64-v8a") + abiFilters.add("armeabi-v7a") + abiFilters.add("x86") + abiFilters.add("x86_64") + } } buildTypes { @@ -26,10 +41,16 @@ android { ) } } + + buildFeatures { + buildConfig = true + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + kotlinOptions { jvmTarget = "17" } @@ -49,9 +70,19 @@ dependencies { 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("com.kakao.sdk:v2-all:2.20.3") + implementation("com.kakao.maps.open:android:2.9.7") implementation("androidx.activity:activity:1.8.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") } + +fun getApiKey(key: String): String { + val properties = Properties() + val localPropertiesFile = rootProject.file("local.properties") + if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.inputStream()) + } + return properties.getProperty(key, "") +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 930d7f16..319c53af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + @@ -25,4 +29,4 @@ - + \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/KakaoApiService.kt b/app/src/main/java/campus/tech/kakao/map/KakaoApiService.kt new file mode 100644 index 00000000..2fa4a266 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/KakaoApiService.kt @@ -0,0 +1,14 @@ +package campus.tech.kakao.map + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface KakaoApiService { + @GET("v2/local/search/category") + fun getPlace( + @Header("Authorization") apiKey: String, + @Query("category_group_code") categoryGroupCode: String, + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/KakaoResponse.kt b/app/src/main/java/campus/tech/kakao/map/KakaoResponse.kt new file mode 100644 index 00000000..f755e404 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/KakaoResponse.kt @@ -0,0 +1,30 @@ +package campus.tech.kakao.map + +import com.google.gson.annotations.SerializedName + +data class KakaoResponse( + val meta: Meta, + val documents: List +) + +data class Meta( + @SerializedName("same_name") val sameName: Any?, + @SerializedName("pageable_count") val pageableCount: Int, + @SerializedName("total_count") val totalCount: Int, + @SerializedName("is_end") val isEnd: Boolean +) + +data class Document( + @SerializedName("place_name") val placeName: String, + val distance: String?, + @SerializedName("place_url") val placeUrl: String, + @SerializedName("category_name") val categoryName: String, + @SerializedName("address_name") val addressName: String, + @SerializedName("road_address_name") val roadAddressName: String, + val id: String, + val phone: String?, + @SerializedName("category_group_code") val categoryGroupCode: String, + @SerializedName("category_group_name") val categoryGroupName: String, + val x: String, + val y: String +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MainActivity.kt b/app/src/main/java/campus/tech/kakao/map/MainActivity.kt index 95b43803..4af0b885 100644 --- a/app/src/main/java/campus/tech/kakao/map/MainActivity.kt +++ b/app/src/main/java/campus/tech/kakao/map/MainActivity.kt @@ -1,11 +1,124 @@ package campus.tech.kakao.map import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.widget.EditText +import android.widget.ImageView +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { + + private lateinit var input: EditText + private lateinit var researchCloseButton: ImageView + private lateinit var tabRecyclerView: RecyclerView + private lateinit var noResultTextView: TextView + private lateinit var recyclerView: RecyclerView + private lateinit var placeRepository: PlaceRepository + private var placeList = mutableListOf() + private var researchList = mutableListOf() + private lateinit var resultAdapter: RecyclerViewAdapter + private lateinit var tapAdapter: TapViewAdapter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + input = findViewById(R.id.input) + researchCloseButton = findViewById(R.id.close_button) + noResultTextView = findViewById(R.id.no_result_textview) + recyclerView = findViewById(R.id.recyclerView) + tabRecyclerView = findViewById(R.id.tab_recyclerview) + + placeRepository = PlaceRepository(this) + placeRepository.reset() + placeList = placeRepository.insertInitialData() + + resultAdapter = RecyclerViewAdapter { + placeRepository.insertLog(it) + addResearchList(it) + } + recyclerView.adapter = resultAdapter + recyclerView.layoutManager = LinearLayoutManager(this) + resultAdapter.submitList(placeList) + + researchList = placeRepository.getResearchEntries().toMutableList() + tapAdapter = TapViewAdapter(researchList) { + placeRepository.deleteResearchEntry(it) + removeResearchList(it) + } + tabRecyclerView.adapter = tapAdapter + tabRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) + + updateTabRecyclerViewVisibility() + + input.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + // ๋ฏธ์‚ฌ์šฉ + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + // ๋ฏธ์‚ฌ์šฉ + } + + override fun afterTextChanged(s: Editable?) { + filterList(s.toString()) + } + }) + + researchCloseButton.setOnClickListener { + input.setText("") + } + } + + private fun filterList(query: String) { + val filteredList = if (query.isEmpty()) { + emptyList() + } else { + placeList.filter { + it.category.category.contains(query, ignoreCase = true) || it.name.contains(query, ignoreCase = true) + } + } + + if (filteredList.isEmpty()) { + noResultTextView.isVisible = true + recyclerView.isGone = true + } else { + noResultTextView.isGone = true + recyclerView.isVisible = true + resultAdapter.submitList(filteredList.toMutableList()) + } + } + + private fun addResearchList(place: Place) { + if (!researchList.contains(place)){ + researchList.add(place) + tapAdapter.notifyItemInserted(researchList.size - 1) + updateTabRecyclerViewVisibility() + } + } + + private fun removeResearchList(it: Place) { + val position = researchList.indexOf(it) + if (position != -1) { + researchList.removeAt(position) + tapAdapter.notifyItemRemoved(position) + updateTabRecyclerViewVisibility() + } + } + + private fun updateTabRecyclerViewVisibility() { + tabRecyclerView.isVisible = placeRepository.hasResearchEntries() + } + + override fun onDestroy() { + super.onDestroy() + input.removeTextChangedListener(null) } } + diff --git a/app/src/main/java/campus/tech/kakao/map/MapViewActivity.kt b/app/src/main/java/campus/tech/kakao/map/MapViewActivity.kt new file mode 100644 index 00000000..d81f160d --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/MapViewActivity.kt @@ -0,0 +1,60 @@ +package campus.tech.kakao.map + +import android.content.Intent +import android.os.Bundle +import android.util.Log +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 + +class MapViewActivity : AppCompatActivity() { + private lateinit var mapView: MapView + private lateinit var searchTextview: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_map_view) + + mapView = findViewById(R.id.map) + searchTextview = findViewById(R.id.search) + + try { + mapView.start(object : MapLifeCycleCallback() { + override fun onMapDestroy() { + Log.d("KakaoMap", "์นด์นด์˜ค๋งต ์ •์ƒ์ข…๋ฃŒ") + } + + override fun onMapError(exception: Exception?) { + Log.e("KakaoMap", "์นด์นด์˜ค๋งต ์ธ์ฆ์‹คํŒจ", exception) + } + }, object : KakaoMapReadyCallback() { + override fun onMapReady(map: KakaoMap) { + Log.d("KakaoMap", "์นด์นด์˜ค๋งต ์ •์ƒ์‹คํ–‰") + } + }) + Log.d("MapViewActivity", "mapView start called") + } catch (e: Exception) { + Log.e("MapViewActivity", "Exception during mapView.start", e) + } + + searchTextview.setOnClickListener { onSearchTextViewClick() } + } + + private fun onSearchTextViewClick() { + startActivity(Intent(this@MapViewActivity, MainActivity::class.java)) + } + override fun onResume() { + super.onResume() + Log.d("MapViewActivity", "onResume called") + mapView.resume() + } + + override fun onPause() { + super.onPause() + Log.d("MapViewActivity", "onPause called") + mapView.pause() + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/MyApplication.kt b/app/src/main/java/campus/tech/kakao/map/MyApplication.kt new file mode 100644 index 00000000..ca5671dc --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/MyApplication.kt @@ -0,0 +1,12 @@ +package campus.tech.kakao.map + +import android.app.Application +import com.kakao.vectormap.KakaoMapSdk + +class MyApplication : Application() { + override fun onCreate() { + super.onCreate() + val appKey = BuildConfig.KAKAO_API_KEY + KakaoMapSdk.init(this, appKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/MyPlaceContract.kt b/app/src/main/java/campus/tech/kakao/map/MyPlaceContract.kt new file mode 100644 index 00000000..bac17b35 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/MyPlaceContract.kt @@ -0,0 +1,22 @@ +package campus.tech.kakao.map + +import android.provider.BaseColumns + +object MyPlaceContract { + object Place : BaseColumns { + const val TABLE_NAME = "place" + const val COLUMN_NAME = "name" + const val COLUMN_IMG = "img" + const val COLUMN_LOCATION = "location" + const val COLUMN_CATEGORY = "category" + } + + object Research : BaseColumns { + const val TABLE_NAME = "research" + const val COLUMN_NAME = "name" + const val COLUMN_IMG = "img" + const val COLUMN_LOCATION = "location" + const val COLUMN_CATEGORY = "category" + } + +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/Place.kt b/app/src/main/java/campus/tech/kakao/map/Place.kt new file mode 100644 index 00000000..ad5af532 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/Place.kt @@ -0,0 +1,11 @@ +package campus.tech.kakao.map + +import androidx.annotation.DrawableRes + +data class Place( + val id: Int? = null, + @DrawableRes val img: Int, + val name: String, + val location: String, + val category: PlaceCategory +) diff --git a/app/src/main/java/campus/tech/kakao/map/PlaceCategory.kt b/app/src/main/java/campus/tech/kakao/map/PlaceCategory.kt new file mode 100644 index 00000000..e1596547 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/PlaceCategory.kt @@ -0,0 +1,14 @@ +package campus.tech.kakao.map + +enum class PlaceCategory(val category: String, val imgId: Int) { + CAFE("์นดํŽ˜", R.drawable.cafe), + PHARMACY("์•ฝ๊ตญ", R.drawable.hospital), + OTHER("๊ธฐํƒ€", R.drawable.location); + + companion object { + fun fromCategory(category: String): PlaceCategory { + return entries.find { it.category == category } ?: OTHER + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/PlaceDbHelper.kt b/app/src/main/java/campus/tech/kakao/map/PlaceDbHelper.kt new file mode 100644 index 00000000..46718a7c --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/PlaceDbHelper.kt @@ -0,0 +1,46 @@ +package campus.tech.kakao.map + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.provider.BaseColumns +import android.util.Log + +class PlaceDbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + companion object { + // If you change the database schema, you must increment the database version. + const val DATABASE_VERSION = 1 + const val DATABASE_NAME = "MyPlace.db" + + private const val SQL_CREATE_PLACE_TABLE = + "CREATE TABLE ${MyPlaceContract.Place.TABLE_NAME} (" + + "${BaseColumns._ID} INTEGER PRIMARY KEY AUTOINCREMENT," + + "${MyPlaceContract.Place.COLUMN_IMG} INTEGER," + + "${MyPlaceContract.Place.COLUMN_NAME} TEXT," + + "${MyPlaceContract.Place.COLUMN_CATEGORY} TEXT," + + "${MyPlaceContract.Place.COLUMN_LOCATION} TEXT)" + + private const val SQL_CREATE_RESEARCH_TABLE = + "CREATE TABLE ${MyPlaceContract.Research.TABLE_NAME} (" + + "${BaseColumns._ID} INTEGER PRIMARY KEY AUTOINCREMENT," + + "${MyPlaceContract.Research.COLUMN_IMG} INTEGER," + + "${MyPlaceContract.Research.COLUMN_NAME} TEXT," + + "${MyPlaceContract.Research.COLUMN_CATEGORY} TEXT," + + "${MyPlaceContract.Research.COLUMN_LOCATION} TEXT)" + + private const val SQL_DELETE_PLACE = "DROP TABLE IF EXISTS ${MyPlaceContract.Place.TABLE_NAME}" + private const val SQL_DELETE_RESEARCH= "DROP TABLE IF EXISTS ${MyPlaceContract.Research.TABLE_NAME}" + } + + override fun onCreate(db: SQLiteDatabase?) { + Log.d("PlaceDbHelper", "Creating tables") + db?.execSQL(SQL_CREATE_PLACE_TABLE) + db?.execSQL(SQL_CREATE_RESEARCH_TABLE) + } + + override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { + db?.execSQL(SQL_DELETE_PLACE) + db?.execSQL(SQL_DELETE_RESEARCH) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/PlaceRepository.kt b/app/src/main/java/campus/tech/kakao/map/PlaceRepository.kt new file mode 100644 index 00000000..b6fbef6c --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/PlaceRepository.kt @@ -0,0 +1,177 @@ +package campus.tech.kakao.map + +import android.content.ContentValues +import android.content.Context +import android.provider.BaseColumns +import android.util.Log +import androidx.core.database.getIntOrNull +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + + +class PlaceRepository(context: Context) { + private val dbHelper = PlaceDbHelper(context) + private var placeList = mutableListOf() + + fun insertPlace(place: Place) { + dbHelper.writableDatabase.use { + val values = ContentValues().apply { + put(MyPlaceContract.Place.COLUMN_IMG, place.img) + put(MyPlaceContract.Place.COLUMN_NAME, place.name) + put(MyPlaceContract.Place.COLUMN_CATEGORY, place.category.category) + put(MyPlaceContract.Place.COLUMN_LOCATION, place.location) + } + + it.insert(MyPlaceContract.Place.TABLE_NAME, null, values) + } + } + + fun insertLog(place: Place) { + dbHelper.writableDatabase.use { db -> + db.query( + MyPlaceContract.Research.TABLE_NAME, + arrayOf(BaseColumns._ID), + "${MyPlaceContract.Research.COLUMN_NAME} = ? AND ${MyPlaceContract.Research.COLUMN_IMG} = ? AND ${MyPlaceContract.Research.COLUMN_LOCATION} = ? AND ${MyPlaceContract.Research.COLUMN_CATEGORY} = ?", + arrayOf(place.name, place.img.toString(), place.location, place.category.category), + null, + null, + null + ).use { cursor -> + if (cursor.moveToFirst()) { + Log.d("PlaceRepository", "Place already exists: ${place.name}") + } else { + val values = ContentValues().apply { + put(MyPlaceContract.Research.COLUMN_NAME, place.name) + put(MyPlaceContract.Research.COLUMN_IMG, place.img) + put(MyPlaceContract.Research.COLUMN_LOCATION, place.location) + put(MyPlaceContract.Research.COLUMN_CATEGORY, place.category.category) + } + db.insert(MyPlaceContract.Research.TABLE_NAME, null, values) + } + } + } + } + + fun reset() { + dbHelper.writableDatabase.use { + it.execSQL("DELETE FROM ${MyPlaceContract.Place.TABLE_NAME}") + } + } + + fun insertInitialData(): MutableList { + //kakao์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์™€์„œ place ๊ฐ์ฒด ์ƒ์„ฑํ•˜๊ธฐ + val apiKey = "KakaoAK " + BuildConfig.KAKAO_REST_API_KEY + RetrofitObject.retrofitService.getPlace(apiKey, "CE7") + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val documentList = response.body()?.documents + documentList?.forEach { + val place = Place(img = R.drawable.cafe, name = it.placeName, location = it.addressName, category = PlaceCategory.CAFE) + placeList.add(place) + insertPlace(place) + } + } else { + val errorBody = response.errorBody()?.string() + Log.d("KakaoAPI", "Error: $errorBody") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("KakaoAPI", "Failure: ${t.message}") + } + }) + + RetrofitObject.retrofitService.getPlace(apiKey, "PM9") + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val documentList = response.body()?.documents + documentList?.forEach { + val place = Place(img = R.drawable.hospital, name = it.placeName, location = it.addressName, category = PlaceCategory.PHARMACY) + placeList.add(place) + insertPlace(place) + } + } else { + val errorBody = response.errorBody()?.string() + Log.d("KakaoAPI", "Error: $errorBody") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("KakaoAPI", "Failure: ${t.message}") + } + }) + return placeList + } + + fun hasResearchEntries() : Boolean { + dbHelper.readableDatabase.use { + it.rawQuery("SELECT COUNT(*) FROM ${MyPlaceContract.Research.TABLE_NAME}", null).use { cursor -> + return if (cursor.moveToFirst()) { + val count = cursor.getIntOrNull(0) ?: 0 + count > 0 + } else { + false + } + } + } + } + + fun getResearchEntries(): List { + val researchList = mutableListOf() + + dbHelper.readableDatabase.use { db -> + db.query( + MyPlaceContract.Research.TABLE_NAME, + arrayOf( + MyPlaceContract.Research.COLUMN_IMG, + MyPlaceContract.Research.COLUMN_NAME, + MyPlaceContract.Research.COLUMN_LOCATION, + MyPlaceContract.Research.COLUMN_CATEGORY + ), + null, null, null, null, null + ).use { cursor -> + while (cursor.moveToNext()) { + val img = cursor.getInt(cursor.getColumnIndexOrThrow(MyPlaceContract.Research.COLUMN_IMG)) + val name = cursor.getString(cursor.getColumnIndexOrThrow(MyPlaceContract.Research.COLUMN_NAME)) + val location = cursor.getString(cursor.getColumnIndexOrThrow(MyPlaceContract.Research.COLUMN_LOCATION)) + val categoryDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(MyPlaceContract.Research.COLUMN_CATEGORY)) + val category = PlaceCategory.fromCategory(categoryDisplayName) + val place = Place(img = img, name = name, location = location, category = category) + researchList.add(place) + } + } + } + + return researchList + } + + fun deleteResearchEntry(place: Place) { + dbHelper.writableDatabase.use { + it.delete( + MyPlaceContract.Research.TABLE_NAME, + "${MyPlaceContract.Research.COLUMN_NAME} = ? AND ${MyPlaceContract.Research.COLUMN_IMG} = ? AND ${MyPlaceContract.Research.COLUMN_LOCATION} = ? AND ${MyPlaceContract.Research.COLUMN_CATEGORY} = ?", + arrayOf(place.name, place.img.toString(), place.location, place.category.category) + ) + } + } + + object RetrofitObject { + val retrofitService: KakaoApiService by lazy { Retrofit.Builder() + .baseUrl("https://dapi.kakao.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(KakaoApiService::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/RecyclerViewAdapter.kt b/app/src/main/java/campus/tech/kakao/map/RecyclerViewAdapter.kt new file mode 100644 index 00000000..8b3cb62d --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/RecyclerViewAdapter.kt @@ -0,0 +1,53 @@ +package campus.tech.kakao.map + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView + +class RecyclerViewAdapter( + private val onItemClicked: (Place) -> Unit +) : ListAdapter( + object : DiffUtil.ItemCallback(){ + override fun areItemsTheSame(oldItem: Place, newItem: Place): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Place, newItem: Place): Boolean { + return oldItem == newItem + } + + } +) { + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val img: ImageView = itemView.findViewById(R.id.place_img) + private val name: TextView = itemView.findViewById(R.id.place_name) + private val location: TextView = itemView.findViewById(R.id.place_location) + private val category: TextView = itemView.findViewById(R.id.place_category) + + fun bind(place: Place) { + img.setImageResource(place.category.imgId) + name.text = place.name + location.text = place.location + category.text = place.category.category + + itemView.setOnClickListener { onItemClicked(place) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.place_card, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val place: Place = getItem(position) + holder.bind(place) + } +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/TapViewAdapter.kt b/app/src/main/java/campus/tech/kakao/map/TapViewAdapter.kt new file mode 100644 index 00000000..f42abe21 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/TapViewAdapter.kt @@ -0,0 +1,44 @@ +package campus.tech.kakao.map + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +class TapViewAdapter( + var researchList: MutableList, + private val onItemRemoved: (Place) -> Unit +) : RecyclerView.Adapter() { + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val cancelButton: ImageView + val placeName: TextView + init { + cancelButton = itemView.findViewById(R.id.tab_close_button) + placeName = itemView.findViewById(R.id.tab_place_textview) + + cancelButton.setOnClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + val item = researchList[position] + onItemRemoved(item) + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.tab_card, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val research: Place = researchList[position] + holder.placeName.text = research.name + } + + override fun getItemCount(): Int = researchList.size +} \ No newline at end of file diff --git a/app/src/main/res/drawable/cafe.png b/app/src/main/res/drawable/cafe.png new file mode 100644 index 00000000..180404f3 Binary files /dev/null and b/app/src/main/res/drawable/cafe.png differ diff --git a/app/src/main/res/drawable/close.png b/app/src/main/res/drawable/close.png new file mode 100644 index 00000000..69428cf1 Binary files /dev/null and b/app/src/main/res/drawable/close.png differ diff --git a/app/src/main/res/drawable/detail.png b/app/src/main/res/drawable/detail.png new file mode 100644 index 00000000..452055ef Binary files /dev/null and b/app/src/main/res/drawable/detail.png differ diff --git a/app/src/main/res/drawable/hospital.png b/app/src/main/res/drawable/hospital.png new file mode 100644 index 00000000..8d39e8f6 Binary files /dev/null and b/app/src/main/res/drawable/hospital.png differ diff --git a/app/src/main/res/drawable/location.png b/app/src/main/res/drawable/location.png new file mode 100644 index 00000000..632dcbc1 Binary files /dev/null and b/app/src/main/res/drawable/location.png differ diff --git a/app/src/main/res/drawable/search_box.xml b/app/src/main/res/drawable/search_box.xml new file mode 100644 index 00000000..c6bb3b39 --- /dev/null +++ b/app/src/main/res/drawable/search_box.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 24d17df2..483085b0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,13 +7,58 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + + + + + + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:visibility="gone" /> diff --git a/app/src/main/res/layout/activity_map_view.xml b/app/src/main/res/layout/activity_map_view.xml new file mode 100644 index 00000000..baef0053 --- /dev/null +++ b/app/src/main/res/layout/activity_map_view.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/place_card.xml b/app/src/main/res/layout/place_card.xml new file mode 100644 index 00000000..b2affbaf --- /dev/null +++ b/app/src/main/res/layout/place_card.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tab_card.xml b/app/src/main/res/layout/tab_card.xml new file mode 100644 index 00000000..98394122 --- /dev/null +++ b/app/src/main/res/layout/tab_card.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 469f947e..07a30410 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,8 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven("https://devrepo.kakao.com/nexus/repository/kakaomap-releases/") + maven("https://devrepo.kakao.com/nexus/content/groups/public/") } } diff --git "a/\352\262\200\354\203\211\352\262\260\352\263\274\354\234\240\354\247\200.webm" "b/\352\262\200\354\203\211\352\262\260\352\263\274\354\234\240\354\247\200.webm" new file mode 100644 index 00000000..59a82a48 Binary files /dev/null and "b/\352\262\200\354\203\211\352\262\260\352\263\274\354\234\240\354\247\200.webm" differ diff --git "a/\352\262\200\354\203\211\354\226\264\354\236\205\353\240\245\354\213\234.png" "b/\352\262\200\354\203\211\354\226\264\354\236\205\353\240\245\354\213\234.png" new file mode 100644 index 00000000..369dba32 Binary files /dev/null and "b/\352\262\200\354\203\211\354\226\264\354\236\205\353\240\245\354\213\234.png" differ diff --git "a/\352\262\260\352\263\274\355\231\224\353\251\264fromapi.png" "b/\352\262\260\352\263\274\355\231\224\353\251\264fromapi.png" new file mode 100644 index 00000000..040ad93e Binary files /dev/null and "b/\352\262\260\352\263\274\355\231\224\353\251\264fromapi.png" differ diff --git "a/\354\203\201\354\204\270\354\235\264\353\246\204\352\262\200\354\203\211\352\260\200\353\212\245.png" "b/\354\203\201\354\204\270\354\235\264\353\246\204\352\262\200\354\203\211\352\260\200\353\212\245.png" new file mode 100644 index 00000000..ff4851da Binary files /dev/null and "b/\354\203\201\354\204\270\354\235\264\353\246\204\352\262\200\354\203\211\352\260\200\353\212\245.png" differ diff --git "a/\354\262\253 \352\262\200\354\203\211\355\231\224\353\251\264.png" "b/\354\262\253 \352\262\200\354\203\211\355\231\224\353\251\264.png" new file mode 100644 index 00000000..6b798fae Binary files /dev/null and "b/\354\262\253 \352\262\200\354\203\211\355\231\224\353\251\264.png" differ diff --git "a/\354\262\253\355\231\224\353\251\264_\354\247\200\353\217\204\355\231\224\353\251\264.png" "b/\354\262\253\355\231\224\353\251\264_\354\247\200\353\217\204\355\231\224\353\251\264.png" new file mode 100644 index 00000000..3bc996c5 Binary files /dev/null and "b/\354\262\253\355\231\224\353\251\264_\354\247\200\353\217\204\355\231\224\353\251\264.png" differ diff --git "a/\354\271\264\353\223\234\355\201\264\353\246\255\354\213\234.png" "b/\354\271\264\353\223\234\355\201\264\353\246\255\354\213\234.png" new file mode 100644 index 00000000..278d686e Binary files /dev/null and "b/\354\271\264\353\223\234\355\201\264\353\246\255\354\213\234.png" differ