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 00000000..69428cf1 Binary files /dev/null and b/app/src/main/res/drawable/close.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/refresh.png b/app/src/main/res/drawable/refresh.png new file mode 100644 index 00000000..432174aa Binary files /dev/null and b/app/src/main/res/drawable/refresh.png differ diff --git a/app/src/main/res/drawable/search.png b/app/src/main/res/drawable/search.png new file mode 100644 index 00000000..452055ef Binary files /dev/null and b/app/src/main/res/drawable/search.png differ 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"> + + + + - + - +