diff --git a/kimhyebeen/Mission07/.gitignore b/kimhyebeen/Mission07/.gitignore new file mode 100644 index 00000000..dd64080c --- /dev/null +++ b/kimhyebeen/Mission07/.gitignore @@ -0,0 +1,148 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle +.gradle/ +build/ + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# User-specific configurations +.idea/ +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/assetWizardSettings.xml +.idea/gradle.xml +.idea/jarRepositories.xml +.idea/navEditor.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio +/app/src/main/java/com/khb/dictionary/SecretKeySet.kt diff --git a/kimhyebeen/Mission07/README.md b/kimhyebeen/Mission07/README.md new file mode 100644 index 00000000..5787190e --- /dev/null +++ b/kimhyebeen/Mission07/README.md @@ -0,0 +1,8 @@ +# Mission07 +The Dictionary Application with MVP pattern + +## Diagram + + +## Reference +* [MVP architecture](https://junghun0.github.io/2019/05/22/android-mvp/) \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/.gitignore b/kimhyebeen/Mission07/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/kimhyebeen/Mission07/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/build.gradle b/kimhyebeen/Mission07/app/build.gradle new file mode 100644 index 00000000..0e219c8a --- /dev/null +++ b/kimhyebeen/Mission07/app/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-android-extensions' + id 'kotlin-kapt' +} + +android { + compileSdkVersion 29 + buildToolsVersion "30.0.2" + + defaultConfig { + applicationId "com.khb.dictionary" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + + // retrofit + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + + // Koin for Android + implementation "org.koin:koin-android:$koin_version" + // Koin AndroidX Scope feature + implementation "org.koin:koin-androidx-scope:$koin_version" + // Koin for Kotlin + implementation "org.koin:koin-core:$koin_version" + // Koin extended & experimental features + implementation "org.koin:koin-core-ext:$koin_version" + // Koin for Unit tests + testImplementation "org.koin:koin-test:$koin_version" +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/proguard-rules.pro b/kimhyebeen/Mission07/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/kimhyebeen/Mission07/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/androidTest/java/com/khb/dictionary/ExampleInstrumentedTest.kt b/kimhyebeen/Mission07/app/src/androidTest/java/com/khb/dictionary/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..b39fa4e9 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/androidTest/java/com/khb/dictionary/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.khb.dictionary + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.khb.dictionary", appContext.packageName) + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/AndroidManifest.xml b/kimhyebeen/Mission07/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b521a4e0 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/DictionaryApplication.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/DictionaryApplication.kt new file mode 100644 index 00000000..0c53cd56 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/DictionaryApplication.kt @@ -0,0 +1,28 @@ +package com.khb.dictionary + +import android.app.Application +import com.khb.dictionary.module.apiModule +import com.khb.dictionary.module.mainAdapterModule +import com.khb.dictionary.module.mainPresenterModule +import com.khb.dictionary.module.networkModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin + +class DictionaryApplication: Application() { + + override fun onCreate() { + super.onCreate() + + startKoin { + androidContext(this@DictionaryApplication) + modules( + listOf( + mainPresenterModule, + networkModule, + apiModule, + mainAdapterModule + ) + ) + } + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BaseActivity.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BaseActivity.kt new file mode 100644 index 00000000..83767ad1 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BaseActivity.kt @@ -0,0 +1,17 @@ +package com.khb.dictionary.base + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +abstract class BaseActivity( + private val layoutId: Int +): AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(layoutId) + + initPresenter() + } + + abstract fun initPresenter() +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BasePresenter.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BasePresenter.kt new file mode 100644 index 00000000..8bde70dd --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BasePresenter.kt @@ -0,0 +1,6 @@ +package com.khb.dictionary.base + +interface BasePresenter { + fun takeView(view: T) + fun dropView() +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BaseView.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BaseView.kt new file mode 100644 index 00000000..c129bbc8 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/BaseView.kt @@ -0,0 +1,5 @@ +package com.khb.dictionary.base + +interface BaseView { + fun showError(error: String) +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/MainContract.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/MainContract.kt new file mode 100644 index 00000000..6fca2322 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/base/MainContract.kt @@ -0,0 +1,14 @@ +package com.khb.dictionary.base + +import com.khb.dictionary.model.DictionaryItem + +interface MainContract { + interface View: BaseView { + fun setAdapter() + fun setButton() + fun showDictionaryList(list: List) + } + interface Presenter: BasePresenter { + fun getDictionaryData(keyword: String) + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainActivity.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainActivity.kt new file mode 100644 index 00000000..989f25ba --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainActivity.kt @@ -0,0 +1,65 @@ +package com.khb.dictionary.main + +import android.annotation.SuppressLint +import android.app.Activity +import android.os.Bundle +import android.view.View +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.recyclerview.widget.LinearLayoutManager +import com.khb.dictionary.R +import com.khb.dictionary.base.BaseActivity +import com.khb.dictionary.base.MainContract +import com.khb.dictionary.model.DictionaryItem +import kotlinx.android.synthetic.main.activity_main.* +import org.koin.android.ext.android.inject + +class MainActivity + : BaseActivity(R.layout.activity_main), MainContract.View { + private val presenter: MainPresenter by inject() + private val mainAdapter: MainAdapter by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setAdapter() + setButton() + } + + override fun initPresenter() { // BaseActivity + presenter.takeView(this) + } + + override fun showError(error: String) { // BaseView + Toast.makeText(this, error, Toast.LENGTH_SHORT).show() + } + + @SuppressLint("ClickableViewAccessibility") + override fun setAdapter() { // MainContract.View + main_recycler_view.apply { + adapter = mainAdapter + layoutManager = LinearLayoutManager(context) + setHasFixedSize(true) + setOnTouchListener { _, _ -> onHideKeypad() } + } + } + + override fun setButton() { // MainContract.View + main_search_button.setOnClickListener { + val keyword = main_search_edit_text.text.toString() + presenter.getDictionaryData(keyword) + } + } + + override fun showDictionaryList(list: List) { // MainContract.View + mainAdapter.setItems(list) + } + + private fun onHideKeypad(): Boolean { + val imm = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + val view: View = currentFocus ?: View(this) + view.clearFocus() + imm.hideSoftInputFromWindow(view.windowToken, 0) + return false + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainAdapter.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainAdapter.kt new file mode 100644 index 00000000..17ef6fd1 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainAdapter.kt @@ -0,0 +1,43 @@ +package com.khb.dictionary.main + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.khb.dictionary.R +import com.khb.dictionary.model.DictionaryItem +import kotlinx.android.synthetic.main.item_search.view.* + +class MainAdapter: RecyclerView.Adapter() { + private var items: List = listOf() + + fun setItems(list: List) { + items = list + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_search, parent, false) + + return ItemViewHolder(view) + } + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) + = holder.onBind(items[position]) + + override fun getItemCount(): Int + = items.size + + inner class ItemViewHolder(itemView: View) + : RecyclerView.ViewHolder(itemView) { + val title: TextView = itemView.item_title_text_view + val description: TextView = itemView.item_description_text_view + + fun onBind(item: DictionaryItem) { + title.text = item.title + description.text = item.description + } + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainPresenter.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainPresenter.kt new file mode 100644 index 00000000..586a7bae --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/main/MainPresenter.kt @@ -0,0 +1,36 @@ +package com.khb.dictionary.main + +import android.util.Log +import com.khb.dictionary.base.MainContract +import com.khb.dictionary.model.SearchApi +import com.khb.dictionary.model.SearchItem +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class MainPresenter( + private val searchApi: SearchApi +): MainContract.Presenter { + private var view: MainContract.View? = null + + override fun getDictionaryData(keyword: String) { + searchApi.getSearchList(keyword) + .enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + Log.e("MainPresenter", t.message ?: "error") + view?.showError("데이터 요청 실패") + } + override fun onResponse(call: Call, response: Response) { + response.body()?.items?.let { view?.showDictionaryList(it) } + } + }) + } + + override fun takeView(view: MainContract.View) { + this.view = view + } + + override fun dropView() { + this.view = null + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/DictionaryItem.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/DictionaryItem.kt new file mode 100644 index 00000000..89be8d7e --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/DictionaryItem.kt @@ -0,0 +1,12 @@ +package com.khb.dictionary.model + +import com.google.gson.annotations.SerializedName + +class DictionaryItem { + @SerializedName("title") + var title: String? = null + @SerializedName("link") + var link: String? = null + @SerializedName("description") + var description: String? = null +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/SearchApi.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/SearchApi.kt new file mode 100644 index 00000000..35beefd5 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/SearchApi.kt @@ -0,0 +1,18 @@ +package com.khb.dictionary.model + +import com.khb.dictionary.SecretKeySet +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Query + +interface SearchApi { + @Headers( + "X-Naver-Client-Id: ${SecretKeySet.clientId}", + "X-Naver-Client-Secret: ${SecretKeySet.clientSecret}" + ) + @GET("search/encyc.json") + fun getSearchList( + @Query("query") query: String + ): Call +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/SearchItem.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/SearchItem.kt new file mode 100644 index 00000000..a8219869 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/model/SearchItem.kt @@ -0,0 +1,8 @@ +package com.khb.dictionary.model + +import com.google.gson.annotations.SerializedName + +class SearchItem { + @SerializedName("items") + var items: List? = null +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/AdapterModule.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/AdapterModule.kt new file mode 100644 index 00000000..06f97c91 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/AdapterModule.kt @@ -0,0 +1,8 @@ +package com.khb.dictionary.module + +import com.khb.dictionary.main.MainAdapter +import org.koin.dsl.module + +val mainAdapterModule = module { + factory { MainAdapter() } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/NetworkModule.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/NetworkModule.kt new file mode 100644 index 00000000..bc71ad02 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/NetworkModule.kt @@ -0,0 +1,28 @@ +package com.khb.dictionary.module + +import com.khb.dictionary.model.SearchApi +import okhttp3.OkHttpClient +import org.koin.dsl.module +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +val networkModule = module { + single { + GsonConverterFactory.create() as Converter.Factory + } + + single { + Retrofit.Builder() + .client(OkHttpClient.Builder().build()) + .baseUrl("https://openapi.naver.com/v1/") + .addConverterFactory(get()) + .build() + } +} + +val apiModule = module { + single(createdAtStart = false) { + get().create(SearchApi::class.java) + } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/PresenterModule.kt b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/PresenterModule.kt new file mode 100644 index 00000000..c0a8301e --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/java/com/khb/dictionary/module/PresenterModule.kt @@ -0,0 +1,10 @@ +package com.khb.dictionary.module + +import com.khb.dictionary.main.MainPresenter +import org.koin.dsl.module + +val mainPresenterModule = module { + single { networkModule } + + factory { MainPresenter(get()) } +} \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/kimhyebeen/Mission07/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/res/drawable/bg_edit_text.xml b/kimhyebeen/Mission07/app/src/main/res/drawable/bg_edit_text.xml new file mode 100644 index 00000000..5e5973d5 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/res/drawable/bg_edit_text.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/res/drawable/bg_item.xml b/kimhyebeen/Mission07/app/src/main/res/drawable/bg_item.xml new file mode 100644 index 00000000..752e05a0 --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/res/drawable/bg_item.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/kimhyebeen/Mission07/app/src/main/res/drawable/ic_launcher_background.xml b/kimhyebeen/Mission07/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kimhyebeen/Mission07/app/src/main/res/layout/activity_main.xml b/kimhyebeen/Mission07/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..aacd99bc --- /dev/null +++ b/kimhyebeen/Mission07/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,62 @@ + + + + + +