diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..3c7772a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..aadc7aa
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# Search My Profile in Github
+
+## branch
+
+### hunki - like develop
+
+### feature_hunki : using to make feat
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 9f9e574..c64e4ed 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
+ id 'kotlin-kapt'
}
android {
@@ -30,16 +31,48 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
+ dataBinding{
+ enabled = true
+ }
+ 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'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+
+ // retrofit
+ implementation 'com.google.code.gson:gson:2.8.5'
+ implementation 'com.squareup.retrofit2:retrofit:2.6.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
+
+ // coroutine
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0'
+
+ // liveData
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
+
+ // viewModel
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$version_lifecycle"
+
+ // ktx
+ implementation "androidx.core:core-ktx:1.3.2"
+ implementation "androidx.fragment:fragment-ktx:1.2.5"
+ implementation "androidx.activity:activity-ktx:1.1.0"
+
+ // Room database
+ implementation "androidx.room:room-runtime:$version_room"
+ kapt "androidx.room:room-compiler:$version_room"
+
+ // Kotlin Extensions and Coroutines support for Room
+ implementation "androidx.room:room-ktx:$version_room"
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e9c8904..0f089cf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,14 +2,16 @@
+
+
+
-
+
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/MainActivity.kt b/app/src/main/java/com/siba/searchmvvmpractice/MainActivity.kt
deleted file mode 100644
index c5fd61e..0000000
--- a/app/src/main/java/com/siba/searchmvvmpractice/MainActivity.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.siba.searchmvvmpractice
-
-import androidx.appcompat.app.AppCompatActivity
-import android.os.Bundle
-
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/injection/Injection.kt b/app/src/main/java/com/siba/searchmvvmpractice/injection/Injection.kt
new file mode 100644
index 0000000..a30e49b
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/injection/Injection.kt
@@ -0,0 +1,26 @@
+package com.siba.searchmvvmpractice.injection
+
+import android.content.Context
+import androidx.lifecycle.ViewModelProvider
+import com.siba.searchmvvmpractice.local.database.SearchTermDatabase
+import com.siba.searchmvvmpractice.remote.RetrofitService
+import com.siba.searchmvvmpractice.remote.api.RetrofitBuilder
+import com.siba.searchmvvmpractice.repository.SearchRepository
+import com.siba.searchmvvmpractice.ui.base.SearchViewModelFactory
+
+object Injection {
+
+ private fun provideRetrofitService(): RetrofitService {
+ return RetrofitBuilder.retrofitService
+ }
+
+ private fun provideMainRepository(context: Context): SearchRepository {
+ val database = SearchTermDatabase.getInstance(context)
+ return SearchRepository(provideRetrofitService(), database.searchTermDao)
+ }
+
+ fun provideSearchViewModelFactory(context: Context): ViewModelProvider.Factory {
+ return SearchViewModelFactory(provideMainRepository(context))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/local/dao/SearchTermDao.kt b/app/src/main/java/com/siba/searchmvvmpractice/local/dao/SearchTermDao.kt
new file mode 100644
index 0000000..0e86ef1
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/local/dao/SearchTermDao.kt
@@ -0,0 +1,20 @@
+package com.siba.searchmvvmpractice.local.dao
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.siba.searchmvvmpractice.local.entity.RecentSearchTerm
+
+@Dao
+interface SearchTermDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertKeyword(recentSearchTerm: RecentSearchTerm)
+
+ @Query("DELETE FROM recent_search_term_table")
+ fun clear()
+
+ @Query("SELECT * FROM recent_search_term_table")
+ fun getAllKeyword(): LiveData>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/local/database/SearchTermDatabase.kt b/app/src/main/java/com/siba/searchmvvmpractice/local/database/SearchTermDatabase.kt
new file mode 100644
index 0000000..f7db529
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/local/database/SearchTermDatabase.kt
@@ -0,0 +1,34 @@
+package com.siba.searchmvvmpractice.local.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import com.siba.searchmvvmpractice.local.dao.SearchTermDao
+import com.siba.searchmvvmpractice.local.entity.RecentSearchTerm
+
+@Database(entities = [RecentSearchTerm::class], version = 1)
+abstract class SearchTermDatabase : RoomDatabase() {
+ abstract val searchTermDao: SearchTermDao
+
+ companion object {
+ @Volatile
+ private var INSTANCE: SearchTermDatabase? = null
+
+ fun getInstance(context: Context): SearchTermDatabase {
+ synchronized(this) {
+ var instance = INSTANCE
+
+ if (instance == null) {
+ instance = Room.databaseBuilder(
+ context.applicationContext,
+ SearchTermDatabase::class.java,
+ "search_keyword_history_database2"
+ ).build()
+ INSTANCE = instance
+ }
+ return instance
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/local/entity/RecentSearchTerm.kt b/app/src/main/java/com/siba/searchmvvmpractice/local/entity/RecentSearchTerm.kt
new file mode 100644
index 0000000..5adb0b7
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/local/entity/RecentSearchTerm.kt
@@ -0,0 +1,12 @@
+package com.siba.searchmvvmpractice.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "recent_search_term_table")
+
+data class RecentSearchTerm(
+ @PrimaryKey(autoGenerate = true)
+ val searchTermId: Int = 0,
+ val keyword: String
+)
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/remote/RetrofitService.kt b/app/src/main/java/com/siba/searchmvvmpractice/remote/RetrofitService.kt
new file mode 100644
index 0000000..7e34253
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/remote/RetrofitService.kt
@@ -0,0 +1,18 @@
+package com.siba.searchmvvmpractice.remote
+
+import com.siba.searchmvvmpractice.remote.model.UserCatalog
+import com.siba.searchmvvmpractice.remote.model.UserRepositoryCatalog
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface RetrofitService {
+ @GET("search/users")
+ suspend fun getUsers(
+ @Query("q") user: String
+ ): UserCatalog
+
+ @GET("search/repositories")
+ suspend fun getRepositories(
+ @Query("q") repositoryName: String
+ ): UserRepositoryCatalog
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/remote/api/RetrofitBuilder.kt b/app/src/main/java/com/siba/searchmvvmpractice/remote/api/RetrofitBuilder.kt
new file mode 100644
index 0000000..f038053
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/remote/api/RetrofitBuilder.kt
@@ -0,0 +1,19 @@
+package com.siba.searchmvvmpractice.remote.api
+
+import com.siba.searchmvvmpractice.remote.RetrofitService
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+object RetrofitBuilder {
+ private const val URL = "https://api.github.com"
+
+ private fun getRetrofit(): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl(URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+
+ val retrofitService: RetrofitService = getRetrofit().create(RetrofitService::class.java)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/remote/model/UserCatalog.kt b/app/src/main/java/com/siba/searchmvvmpractice/remote/model/UserCatalog.kt
new file mode 100644
index 0000000..d984d5a
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/remote/model/UserCatalog.kt
@@ -0,0 +1,53 @@
+package com.siba.searchmvvmpractice.remote.model
+
+import com.google.gson.annotations.SerializedName
+
+data class UserCatalog(
+ @SerializedName("total_count")
+ val total_count: Int,
+ @SerializedName("incomplete_results")
+ val incomplete_results: Boolean,
+ @SerializedName("items")
+ val users: List
+)
+
+data class Users(
+ @SerializedName("login")
+ val login: String,
+ @SerializedName("id")
+ val id: Int,
+ @SerializedName("node_id")
+ val node_id: String,
+ @SerializedName("avatar_url")
+ val avatar_url: String,
+ @SerializedName("gravatar_id")
+ val gravatar_id: String,
+ @SerializedName("url")
+ val url: String,
+ @SerializedName("html_url")
+ val html_url: String,
+ @SerializedName("followers_url")
+ val followers_url: String,
+ @SerializedName("following_url")
+ val following_url: String,
+ @SerializedName("gists_url")
+ val gists_url: String,
+ @SerializedName("starred_url")
+ val starred_url: String,
+ @SerializedName("subscriptions_url")
+ val subscriptions_url: String,
+ @SerializedName("organizations_url")
+ val organizations_url: String,
+ @SerializedName("repos_url")
+ val repos_url: String,
+ @SerializedName("events_url")
+ val events_url: String,
+ @SerializedName("received_events_url")
+ val received_events_url: String,
+ @SerializedName("type")
+ val type: String,
+ @SerializedName("site_admin")
+ val site_admin: Boolean,
+ @SerializedName("score")
+ val score: Double
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/remote/model/UserRepositoryCatalog.kt b/app/src/main/java/com/siba/searchmvvmpractice/remote/model/UserRepositoryCatalog.kt
new file mode 100644
index 0000000..8114b8e
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/remote/model/UserRepositoryCatalog.kt
@@ -0,0 +1,19 @@
+package com.siba.searchmvvmpractice.remote.model
+
+import com.google.gson.annotations.SerializedName
+
+data class UserRepositoryCatalog(
+ @SerializedName("total_count")
+ val total_count: Int,
+ @SerializedName("incomplete_results")
+ val incomplete_results: Boolean,
+ @SerializedName("items")
+ val userRepository: List
+)
+
+data class UserRepository(
+ @SerializedName("full_name")
+ val full_name: String,
+ @SerializedName("html_url")
+ val html_url: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/repository/SearchRepository.kt b/app/src/main/java/com/siba/searchmvvmpractice/repository/SearchRepository.kt
new file mode 100644
index 0000000..0185cc6
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/repository/SearchRepository.kt
@@ -0,0 +1,22 @@
+package com.siba.searchmvvmpractice.repository
+
+import com.siba.searchmvvmpractice.local.dao.SearchTermDao
+import com.siba.searchmvvmpractice.local.entity.RecentSearchTerm
+import com.siba.searchmvvmpractice.remote.RetrofitService
+import com.siba.searchmvvmpractice.remote.model.UserCatalog
+import com.siba.searchmvvmpractice.remote.model.UserRepositoryCatalog
+
+class SearchRepository(
+ private val retrofitService: RetrofitService,
+ private val searchTermDao: SearchTermDao
+) {
+ suspend fun fetchUser(userName: String): UserCatalog = retrofitService.getUsers(userName)
+
+ suspend fun fetchRepo(repositoryName: String): UserRepositoryCatalog = retrofitService.getRepositories(repositoryName)
+
+ suspend fun insert(recentSearchTerm: RecentSearchTerm) {
+ searchTermDao.insertKeyword(recentSearchTerm)
+ }
+
+ fun getAll() = searchTermDao.getAllKeyword()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/RepoAdapter.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/RepoAdapter.kt
new file mode 100644
index 0000000..07c765d
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/RepoAdapter.kt
@@ -0,0 +1,34 @@
+package com.siba.searchmvvmpractice.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.recyclerview.widget.RecyclerView
+import com.siba.searchmvvmpractice.BR
+import com.siba.searchmvvmpractice.R
+import com.siba.searchmvvmpractice.databinding.RepoItemBinding
+import com.siba.searchmvvmpractice.remote.model.UserRepository
+
+class RepoAdapter : RecyclerView.Adapter.RepoViewHolder>() {
+ var data = mutableListOf()
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepoViewHolder =
+ RepoViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.repo_item, parent, false)
+ )
+
+ override fun onBindViewHolder(holder: RepoViewHolder, position: Int) {
+ holder.bind(data[position])
+ }
+
+ override fun getItemCount(): Int = data.size
+
+ inner class RepoViewHolder(itemView: View) :
+ RecyclerView.ViewHolder(itemView) {
+ private val binding: B = DataBindingUtil.bind(itemView)!!
+ fun bind(userRepository: UserRepository) {
+ binding.setVariable(BR.userRepository, userRepository)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/SearchTermAdapter.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/SearchTermAdapter.kt
new file mode 100644
index 0000000..78bcc1b
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/SearchTermAdapter.kt
@@ -0,0 +1,40 @@
+package com.siba.searchmvvmpractice.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.recyclerview.widget.RecyclerView
+import com.siba.searchmvvmpractice.BR
+import com.siba.searchmvvmpractice.R
+import com.siba.searchmvvmpractice.databinding.SearchTermItemBinding
+import com.siba.searchmvvmpractice.local.entity.RecentSearchTerm
+
+class SearchTermAdapter :
+ RecyclerView.Adapter.SearchTermViewHolder>() {
+ var data = emptyList()
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchTermViewHolder =
+ SearchTermViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.search_term_item, parent, false)
+ )
+
+ override fun onBindViewHolder(holder: SearchTermViewHolder, position: Int) {
+ holder.bind(data[position])
+ }
+
+ override fun getItemCount(): Int = data.size
+
+ internal fun setData(recentSearchTerm: List) {
+ this.data = recentSearchTerm
+ notifyDataSetChanged()
+ }
+
+ inner class SearchTermViewHolder(itemView: View) :
+ RecyclerView.ViewHolder(itemView) {
+ private val binding: B = DataBindingUtil.bind(itemView)!!
+ fun bind(recentSearchTerm: RecentSearchTerm) {
+ binding.setVariable(BR.searchTerm, recentSearchTerm)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/UserAdapter.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/UserAdapter.kt
new file mode 100644
index 0000000..71296a2
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/UserAdapter.kt
@@ -0,0 +1,34 @@
+package com.siba.searchmvvmpractice.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.recyclerview.widget.RecyclerView
+import com.siba.searchmvvmpractice.BR
+import com.siba.searchmvvmpractice.R
+import com.siba.searchmvvmpractice.databinding.UserItemBinding
+import com.siba.searchmvvmpractice.remote.model.Users
+
+class UserAdapter : RecyclerView.Adapter.UserViewHolder>() {
+ var data = mutableListOf()
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder =
+ UserViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.user_item, parent, false)
+ )
+
+ override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
+ holder.bind(data[position])
+ }
+
+ override fun getItemCount(): Int = data.size
+
+ inner class UserViewHolder(itemView: View) :
+ RecyclerView.ViewHolder(itemView) {
+ private val binding: B = DataBindingUtil.bind(itemView)!!
+ fun bind(users: Users) {
+ binding.setVariable(BR.users, users)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/ViewPagerAdapter.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/ViewPagerAdapter.kt
new file mode 100644
index 0000000..f629580
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/adapter/ViewPagerAdapter.kt
@@ -0,0 +1,20 @@
+package com.siba.searchmvvmpractice.ui.adapter
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import com.siba.searchmvvmpractice.ui.presentation.fragment.SearchRepoFragment
+import com.siba.searchmvvmpractice.ui.presentation.fragment.SearchUserFragment
+
+class ViewPagerAdapter(fm: FragmentManager) :
+ FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ override fun getCount(): Int = 2
+
+ override fun getItem(position: Int): Fragment {
+ return when (position) {
+ 0 -> SearchUserFragment()
+ else -> SearchRepoFragment()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/base/SearchViewModelFactory.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/base/SearchViewModelFactory.kt
new file mode 100644
index 0000000..86b698f
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/base/SearchViewModelFactory.kt
@@ -0,0 +1,22 @@
+package com.siba.searchmvvmpractice.ui.base
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.siba.searchmvvmpractice.repository.SearchRepository
+import com.siba.searchmvvmpractice.ui.viewmodel.SearchViewModel
+
+@Suppress("UNCHECKED_CAST")
+class SearchViewModelFactory(private val repository: SearchRepository) :
+ ViewModelProvider.NewInstanceFactory() {
+ override fun create(modelClass: Class): T {
+ /* if (modelClass.isAssignableFrom(SearchViewModel::class.java)) {
+ return SearchViewModel(repository) as T
+ }
+ throw IllegalArgumentException("Unknown class name")*/
+
+ // SearchViewModel(repository) as T에서 T는 ViewModel 로 타입이 일치한다. 하지만 에러가 나타나 suppress 해주었다.
+ require(modelClass.isAssignableFrom(SearchViewModel::class.java)){"Unknown class name"}
+ return SearchViewModel(repository) as T
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/activity/SearchActivity.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/activity/SearchActivity.kt
new file mode 100644
index 0000000..4f77377
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/activity/SearchActivity.kt
@@ -0,0 +1,103 @@
+package com.siba.searchmvvmpractice.ui.presentation.activity
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SearchView
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.siba.searchmvvmpractice.R
+import com.siba.searchmvvmpractice.databinding.ActivitySearchBinding
+import com.siba.searchmvvmpractice.databinding.SearchTermItemBinding
+import com.siba.searchmvvmpractice.injection.Injection
+import com.siba.searchmvvmpractice.ui.adapter.SearchTermAdapter
+import com.siba.searchmvvmpractice.ui.adapter.ViewPagerAdapter
+import com.siba.searchmvvmpractice.ui.viewmodel.SearchViewModel
+
+class SearchActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivitySearchBinding
+ private lateinit var viewModel: SearchViewModel
+
+ private lateinit var searchTermAdapter: SearchTermAdapter
+
+ // TODO : 1. 최근검색어가 2개씩 저장되는 issue 처리
+ // TODO : 2. 서버에서 데이터가져오는걸 실패할 경우 앱이 죽지 말고 있어야함 , 에러처리 주체도 생각해봐야 할 듯
+ // TODO : 3. OFFLINE 캐싱
+ // TODO : 4. base Factory rename to factory
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_search)
+ initViewModel()
+ initViews()
+ setViewPagerAdapter(supportFragmentManager)
+ setSearchView(binding.searchviewMain)
+ setSearchTermRecyclerView()
+ binding.lifecycleOwner = this
+
+ }
+
+ private fun initViewModel() {
+ viewModel = ViewModelProvider(this, Injection.provideSearchViewModelFactory(this)).get(
+ SearchViewModel::class.java
+ )
+ }
+
+ private fun initViews() {
+ searchTermAdapter = SearchTermAdapter()
+ viewModel.allSearch.observe(this) {
+ searchTermAdapter.setData(it)
+ }
+ searchTermAdapter.notifyDataSetChanged()
+ }
+
+ private fun setSearchTermRecyclerView() {
+ binding.searchTermRecyclerviewMain.apply {
+ adapter = searchTermAdapter
+ layoutManager = LinearLayoutManager(this@SearchActivity)
+ addItemDecoration(
+ DividerItemDecoration(
+ this@SearchActivity,
+ LinearLayoutManager.VERTICAL
+ )
+ )
+ }
+ }
+
+ private fun setSearchView(searchView: SearchView) {
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ viewModel.userName.value = query!!
+ search()
+ viewModel.saveSearchTerm()
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ return false
+ }
+ })
+ }
+
+ private fun setViewPagerAdapter(fm: FragmentManager) {
+ binding.viewpagerMain.apply {
+ adapter = ViewPagerAdapter(fm)
+ }
+ binding.tabMain.apply {
+ setupWithViewPager(binding.viewpagerMain)
+ getTabAt(0)?.text = "User"
+ getTabAt(1)?.text = "Repository"
+ }
+ }
+
+ private fun search() {
+ if (binding.tabMain.selectedTabPosition == 0)
+ viewModel.searchUser()
+ else
+ viewModel.searchRepo()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/fragment/SearchRepoFragment.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/fragment/SearchRepoFragment.kt
new file mode 100644
index 0000000..3b5cd21
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/fragment/SearchRepoFragment.kt
@@ -0,0 +1,56 @@
+package com.siba.searchmvvmpractice.ui.presentation.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.siba.searchmvvmpractice.R
+import com.siba.searchmvvmpractice.databinding.FragmentSearchRepoBinding
+import com.siba.searchmvvmpractice.databinding.RepoItemBinding
+import com.siba.searchmvvmpractice.remote.model.UserRepository
+import com.siba.searchmvvmpractice.ui.adapter.RepoAdapter
+import com.siba.searchmvvmpractice.ui.viewmodel.SearchViewModel
+
+class SearchRepoFragment : Fragment() {
+ private lateinit var binding: FragmentSearchRepoBinding
+
+ private val viewModel: SearchViewModel by activityViewModels()
+ private lateinit var repoAdapter: RepoAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_repo, container, false)
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ binding.lifecycleOwner = viewLifecycleOwner
+ repoAdapter = RepoAdapter()
+ setAdapter()
+ setObserver()
+ }
+
+ private fun setObserver() {
+ viewModel.githubRepo.observe(viewLifecycleOwner) {
+ repoAdapter.data = it.userRepository as MutableList
+ repoAdapter.notifyDataSetChanged()
+ }
+ }
+
+ private fun setAdapter() {
+ binding.searchRepoRecyclerview.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = repoAdapter
+ addItemDecoration(DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL))
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/fragment/SearchUserFragment.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/fragment/SearchUserFragment.kt
new file mode 100644
index 0000000..05b781c
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/presentation/fragment/SearchUserFragment.kt
@@ -0,0 +1,56 @@
+package com.siba.searchmvvmpractice.ui.presentation.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.siba.searchmvvmpractice.R
+import com.siba.searchmvvmpractice.databinding.FragmentSearchUserBinding
+import com.siba.searchmvvmpractice.databinding.UserItemBinding
+import com.siba.searchmvvmpractice.remote.model.Users
+import com.siba.searchmvvmpractice.ui.adapter.UserAdapter
+import com.siba.searchmvvmpractice.ui.viewmodel.SearchViewModel
+
+class SearchUserFragment : Fragment() {
+ private lateinit var binding: FragmentSearchUserBinding
+
+ private val viewModel: SearchViewModel by activityViewModels()
+ private lateinit var userAdapter: UserAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_user, container, false)
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ binding.lifecycleOwner = viewLifecycleOwner
+ userAdapter = UserAdapter()
+ setObserver()
+ setAdapter()
+ }
+
+ private fun setAdapter() {
+ binding.searchUserRecyclerview.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = userAdapter
+ addItemDecoration(DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL))
+ }
+ }
+
+ fun setObserver() {
+ viewModel.githubUser.observe(viewLifecycleOwner) {
+ userAdapter.data = it.users as MutableList
+ userAdapter.notifyDataSetChanged()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/siba/searchmvvmpractice/ui/viewmodel/SearchViewModel.kt b/app/src/main/java/com/siba/searchmvvmpractice/ui/viewmodel/SearchViewModel.kt
new file mode 100644
index 0000000..e6abab4
--- /dev/null
+++ b/app/src/main/java/com/siba/searchmvvmpractice/ui/viewmodel/SearchViewModel.kt
@@ -0,0 +1,57 @@
+package com.siba.searchmvvmpractice.ui.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.siba.searchmvvmpractice.local.entity.RecentSearchTerm
+import com.siba.searchmvvmpractice.remote.model.UserCatalog
+import com.siba.searchmvvmpractice.remote.model.UserRepositoryCatalog
+import com.siba.searchmvvmpractice.repository.SearchRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class SearchViewModel(
+ private val repository: SearchRepository
+) : ViewModel() {
+
+ private val _userName = MutableLiveData()
+ val userName: MutableLiveData
+ get() = _userName
+
+ private val _githubUser = MutableLiveData()
+ val githubUser: MutableLiveData
+ get() = _githubUser
+
+ private val _githubRepo = MutableLiveData()
+ val githubRepo: MutableLiveData
+ get() = _githubRepo
+
+ var allSearch: LiveData> = repository.getAll()
+
+ fun searchUser() = viewModelScope.launch {
+ try {
+ _githubUser.value = repository.fetchUser(userName.value.toString())
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ }
+ }
+
+ fun searchRepo() = viewModelScope.launch {
+ try {
+ _githubRepo.value = repository.fetchRepo(userName.value.toString())
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ }
+ }
+
+ fun saveSearchTerm() = viewModelScope.launch(Dispatchers.IO) {
+ val recentSearchTerm = RecentSearchTerm(keyword = userName.value.toString())
+ repository.insert(recentSearchTerm)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..44be649
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_recent_keyword.xml b/app/src/main/res/drawable/ic_recent_keyword.xml
new file mode 100644
index 0000000..2b4ff02
--- /dev/null
+++ b/app/src/main/res/drawable/ic_recent_keyword.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 4fc2444..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml
new file mode 100644
index 0000000..07b011a
--- /dev/null
+++ b/app/src/main/res/layout/activity_search.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_search_repo.xml b/app/src/main/res/layout/fragment_search_repo.xml
new file mode 100644
index 0000000..146adf1
--- /dev/null
+++ b/app/src/main/res/layout/fragment_search_repo.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_search_user.xml b/app/src/main/res/layout/fragment_search_user.xml
new file mode 100644
index 0000000..7eb972e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_search_user.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/repo_item.xml b/app/src/main/res/layout/repo_item.xml
new file mode 100644
index 0000000..2328454
--- /dev/null
+++ b/app/src/main/res/layout/repo_item.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_term_item.xml b/app/src/main/res/layout/search_term_item.xml
new file mode 100644
index 0000000..c60a510
--- /dev/null
+++ b/app/src/main/res/layout/search_term_item.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/user_item.xml b/app/src/main/res/layout/user_item.xml
new file mode 100644
index 0000000..791727b
--- /dev/null
+++ b/app/src/main/res/layout/user_item.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 101c85f..235d3d9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,5 @@
SearchMVVMPractice
+
+ Hello blank fragment
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 24d1382..73280a7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,12 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.10"
+ ext{
+ version_kotlin = "1.3.72"
+ version_core = "1.3.1"
+ version_constraint_layout = "2.0.0-rc1"
+ version_lifecycle_extensions = "2.2.0"
+ version_material = "1.2.0"
+ version_navigation = "2.3.0"
+ version_lifecycle = "2.2.0"
+ version_room = "2.2.5"
+ }
repositories {
google()
jcenter()
}
dependencies {
- classpath "com.android.tools.build:gradle:4.1.0"
+ classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong