diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f401ea6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'androidx.navigation.safeargs' +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.ankit.trendinggit" + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + dataBinding { + enabled = true + } +} + +androidExtensions { + experimental = true +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.0-beta01' + implementation 'androidx.constraintlayout:constraintlayout:1.1.2' + + // Navigation component + implementation "android.arch.navigation:navigation-fragment:$rootProject.nav_version" // use -ktx for Kotlin + implementation "android.arch.navigation:navigation-ui:$rootProject.nav_version" // use -ktx for Kotlin + implementation "android.arch.navigation:navigation-runtime-ktx:$rootProject.nav_version" // use -ktx for Kotlin + implementation "android.arch.work:work-runtime-ktx:$rootProject.workVersion" // use -ktx for Kotlin + + // Anko + implementation "org.jetbrains.anko:anko:$rootProject.anko_version" + implementation "org.jetbrains.anko:anko-commons:$rootProject.anko_version" + + // Retrofit + implementation 'com.squareup.retrofit2:retrofit:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.3.0' + implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' + + // Databinding compiler + kapt 'com.android.databinding:compiler:3.2.0-alpha10' +} + +kotlin { + experimental { + coroutines "enable" + } +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4ce38f9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/TrendingGitApp.kt b/app/src/main/java/com/ankit/trendinggit/TrendingGitApp.kt new file mode 100644 index 0000000..27c3d6e --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/TrendingGitApp.kt @@ -0,0 +1,15 @@ +package com.ankit.trendinggit + +import android.app.Application + +class TrendingGitApp : Application() { + + override fun onCreate() { + super.onCreate() + instance = this + } + + companion object { + lateinit var instance: TrendingGitApp + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/model/ApiResponse.kt b/app/src/main/java/com/ankit/trendinggit/model/ApiResponse.kt new file mode 100644 index 0000000..eeb4df7 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/model/ApiResponse.kt @@ -0,0 +1,112 @@ +package com.ankit.trendinggit.model + +data class GitResponse( + val total_count: Int, + val incomplete_results: Boolean, + val items: List +) + +data class Item( + val id: Int, + val node_id: String, + val name: String, + val full_name: String, + val owner: Owner, + val private: Boolean, + val html_url: String, + val description: String, + val fork: Boolean, + val url: String, + val forks_url: String, + val keys_url: String, + val collaborators_url: String, + val teams_url: String, + val hooks_url: String, + val issue_events_url: String, + val events_url: String, + val assignees_url: String, + val branches_url: String, + val tags_url: String, + val blobs_url: String, + val git_tags_url: String, + val git_refs_url: String, + val trees_url: String, + val statuses_url: String, + val languages_url: String, + val stargazers_url: String, + val contributors_url: String, + val subscribers_url: String, + val subscription_url: String, + val commits_url: String, + val git_commits_url: String, + val comments_url: String, + val issue_comment_url: String, + val contents_url: String, + val compare_url: String, + val merges_url: String, + val archive_url: String, + val downloads_url: String, + val issues_url: String, + val pulls_url: String, + val milestones_url: String, + val notifications_url: String, + val labels_url: String, + val releases_url: String, + val deployments_url: String, + val created_at: String, + val updated_at: String, + val pushed_at: String, + val git_url: String, + val ssh_url: String, + val clone_url: String, + val svn_url: String, + val homepage: String, + val size: Int, + val stargazers_count: Int, + val watchers_count: Int, + val language: String, + val has_issues: Boolean, + val has_projects: Boolean, + val has_downloads: Boolean, + val has_wiki: Boolean, + val has_pages: Boolean, + val forks_count: Int, + val mirror_url: Any, + val archived: Boolean, + val open_issues_count: Int, + val license: License, + val forks: Int, + val open_issues: Int, + val watchers: Int, + val default_branch: String, + val score: Double +) + +data class Owner( + val login: String, + val id: Int, + val node_id: String, + val avatar_url: String, + val gravatar_id: String, + val url: String, + val html_url: String, + val followers_url: String, + val following_url: String, + val gists_url: String, + val starred_url: String, + val subscriptions_url: String, + val organizations_url: String, + val repos_url: String, + val events_url: String, + val received_events_url: String, + val type: String, + val site_admin: Boolean +) + +data class License( + val key: String, + val name: String, + val spdx_id: String, + val url: String, + val node_id: String +) \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/model/api/ApiClient.kt b/app/src/main/java/com/ankit/trendinggit/model/api/ApiClient.kt new file mode 100644 index 0000000..4aaad07 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/model/api/ApiClient.kt @@ -0,0 +1,54 @@ +package com.ankit.trendinggit.model.api + +import com.ankit.trendinggit.view.utils.Constants.Companion.BASE_URL +import com.ankit.trendinggit.view.utils.Constants.Companion.DEBUG +import com.ankit.trendinggit.view.utils.Constants.Companion.REQUEST_TIMEOUT_DURATION +import com.google.gson.GsonBuilder +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +object ApiClient { + + val instance: ApiService = Retrofit.Builder().run { + val gson = GsonBuilder() + .enableComplexMapKeySerialization() + .setPrettyPrinting() + .create() + + baseUrl(BASE_URL) + addConverterFactory(GsonConverterFactory.create(gson)) + client(createRequestInterceptorClient()) + build() + }.create(ApiService::class.java) + + + private fun createRequestInterceptorClient(): OkHttpClient { + val interceptor = Interceptor { chain -> + val original = chain.request() + val requestBuilder = original.newBuilder() + val request = requestBuilder.build() + chain.proceed(request) + } + + return if (DEBUG) { + OkHttpClient.Builder() + .addInterceptor(interceptor) + .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) + .connectTimeout(REQUEST_TIMEOUT_DURATION.toLong(), TimeUnit.SECONDS) + .readTimeout(REQUEST_TIMEOUT_DURATION.toLong(), TimeUnit.SECONDS) + .writeTimeout(REQUEST_TIMEOUT_DURATION.toLong(), TimeUnit.SECONDS) + .build() + } else { + OkHttpClient.Builder() + .addInterceptor(interceptor) + .connectTimeout(REQUEST_TIMEOUT_DURATION.toLong(), TimeUnit.SECONDS) + .readTimeout(REQUEST_TIMEOUT_DURATION.toLong(), TimeUnit.SECONDS) + .writeTimeout(REQUEST_TIMEOUT_DURATION.toLong(), TimeUnit.SECONDS) + .build() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/model/api/ApiService.kt b/app/src/main/java/com/ankit/trendinggit/model/api/ApiService.kt new file mode 100644 index 0000000..5e61c31 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/model/api/ApiService.kt @@ -0,0 +1,12 @@ +package com.ankit.trendinggit.model.api + +import com.ankit.trendinggit.model.GitResponse +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query + +interface ApiService { + + @GET("search/repositories") + fun getRepo(@Query("q") search: String = "trending"): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/model/api/RepoRepository.kt b/app/src/main/java/com/ankit/trendinggit/model/api/RepoRepository.kt new file mode 100644 index 0000000..6a553fc --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/model/api/RepoRepository.kt @@ -0,0 +1,34 @@ +package com.ankit.trendinggit.model.api + +import com.ankit.trendinggit.model.GitResponse +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class RepoRepository { + + // GET repo list + fun getRepoList(onResult: (isSuccess: Boolean, response: GitResponse?) -> Unit) { + + ApiClient.instance.getRepo().enqueue(object :Callback { + override fun onResponse(call: Call?, response: Response?) { + if (response != null && response.isSuccessful) + onResult(true, response.body()!!) + else + onResult(false, null) + } + + override fun onFailure(call: Call?, t: Throwable?) { + onResult(false, null) + } + + }) + } + + companion object { + private var INSTANCE: RepoRepository? = null + fun getInstance() = INSTANCE ?: RepoRepository().also { + INSTANCE = it + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/view/adapter/RepoListAdapter.kt b/app/src/main/java/com/ankit/trendinggit/view/adapter/RepoListAdapter.kt new file mode 100644 index 0000000..e5a01da --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/adapter/RepoListAdapter.kt @@ -0,0 +1,30 @@ +package com.ankit.trendinggit.view.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.ankit.trendinggit.databinding.ViewRepoListItemBinding +import com.ankit.trendinggit.model.Item +import com.ankit.trendinggit.view.adapter.viewHolders.RepoListViewHolder +import com.ankit.trendinggit.view.ui.repolist.RepoListViewModel + +class RepoListAdapter(private val repoListViewModel: RepoListViewModel) : RecyclerView.Adapter() { + var repoList: List = emptyList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepoListViewHolder { + val inflater = LayoutInflater.from(parent.context) + val dataBinding = ViewRepoListItemBinding.inflate(inflater, parent, false) + return RepoListViewHolder(dataBinding, repoListViewModel) + } + + override fun getItemCount() = repoList.size + + override fun onBindViewHolder(holder: RepoListViewHolder, position: Int) { + holder.setup(repoList[position]) + } + + fun updateRepoList(repoList: List) { + this.repoList = repoList + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/view/adapter/viewHolders/RepoListViewHolder.kt b/app/src/main/java/com/ankit/trendinggit/view/adapter/viewHolders/RepoListViewHolder.kt new file mode 100644 index 0000000..af3796e --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/adapter/viewHolders/RepoListViewHolder.kt @@ -0,0 +1,16 @@ +package com.ankit.trendinggit.view.adapter.viewHolders + +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView +import com.ankit.trendinggit.BR +import com.ankit.trendinggit.model.Item +import com.ankit.trendinggit.view.ui.repolist.RepoListViewModel + +class RepoListViewHolder constructor(private val dataBinding: ViewDataBinding, private val repoListViewModel: RepoListViewModel) + : RecyclerView.ViewHolder(dataBinding.root) { + + fun setup(itemData: Item) { + dataBinding.setVariable(BR.itemData, itemData) + dataBinding.executePendingBindings() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/view/base/BaseViewModel.kt b/app/src/main/java/com/ankit/trendinggit/view/base/BaseViewModel.kt new file mode 100644 index 0000000..c5323c4 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/base/BaseViewModel.kt @@ -0,0 +1,16 @@ +package com.ankit.trendinggit.view.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.ankit.trendinggit.TrendingGitApp + +open class BaseViewModel : ViewModel() { + + val empty = MutableLiveData().apply { value = false } + + val dataLoading = MutableLiveData().apply { value = false } + + val toastMessage = MutableLiveData() + + val appContext get() = TrendingGitApp.instance +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/view/ui/MainActivity.kt b/app/src/main/java/com/ankit/trendinggit/view/ui/MainActivity.kt new file mode 100644 index 0000000..411c87b --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/ui/MainActivity.kt @@ -0,0 +1,20 @@ +package com.ankit.trendinggit.view.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.findNavController +import androidx.navigation.ui.NavigationUI +import com.ankit.trendinggit.R +import kotlinx.android.synthetic.main.activity_main.* + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + setSupportActionBar(toolbar) + NavigationUI.setupActionBarWithNavController(this, findNavController(R.id.main_nav_fragment)) + } + + override fun onSupportNavigateUp() = findNavController(R.id.main_nav_fragment).navigateUp() +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/view/ui/repolist/RepoListFragment.kt b/app/src/main/java/com/ankit/trendinggit/view/ui/repolist/RepoListFragment.kt new file mode 100644 index 0000000..c456829 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/ui/repolist/RepoListFragment.kt @@ -0,0 +1,52 @@ +package com.ankit.trendinggit.view.ui.repolist + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.ankit.trendinggit.databinding.FragmentRepoListBinding +import com.ankit.trendinggit.view.adapter.RepoListAdapter +import kotlinx.android.synthetic.main.fragment_repo_list.* + +class RepoListFragment : Fragment() { + + private lateinit var viewDataBinding: FragmentRepoListBinding + private lateinit var adapter: RepoListAdapter + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + viewDataBinding = FragmentRepoListBinding.inflate(inflater, container, false).apply { + viewmodel = ViewModelProviders.of(this@RepoListFragment).get(RepoListViewModel::class.java) + setLifecycleOwner(viewLifecycleOwner) + } + return viewDataBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewDataBinding.viewmodel?.fetchRepoList() + + setupAdapter() + setupObservers() + } + private fun setupObservers() { + viewDataBinding.viewmodel?.repoListLive?.observe(viewLifecycleOwner, Observer { + adapter.updateRepoList(it) + }) + } + + private fun setupAdapter() { + val viewModel = viewDataBinding.viewmodel + if (viewModel != null) { + adapter = RepoListAdapter(viewDataBinding.viewmodel!!) + val layoutManager = LinearLayoutManager(activity) + repo_list_rv.layoutManager = layoutManager + repo_list_rv.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation)) + repo_list_rv.adapter = adapter + } + } +} diff --git a/app/src/main/java/com/ankit/trendinggit/view/ui/repolist/RepoListViewModel.kt b/app/src/main/java/com/ankit/trendinggit/view/ui/repolist/RepoListViewModel.kt new file mode 100644 index 0000000..91056e4 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/ui/repolist/RepoListViewModel.kt @@ -0,0 +1,23 @@ +package com.ankit.trendinggit.view.ui.repolist + +import androidx.lifecycle.MutableLiveData +import com.ankit.trendinggit.model.Item +import com.ankit.trendinggit.model.api.RepoRepository +import com.ankit.trendinggit.view.base.BaseViewModel + +class RepoListViewModel : BaseViewModel() { + val repoListLive = MutableLiveData>() + + fun fetchRepoList() { + dataLoading.value = true + RepoRepository.getInstance().getRepoList { isSuccess, response -> + dataLoading.value = false + if(isSuccess) { + repoListLive.value = response?.items + empty.value = false + } else { + empty.value = true + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ankit/trendinggit/view/utils/Constants.kt b/app/src/main/java/com/ankit/trendinggit/view/utils/Constants.kt new file mode 100644 index 0000000..4ec9615 --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/utils/Constants.kt @@ -0,0 +1,9 @@ +package com.ankit.trendinggit.view.utils + +class Constants { + companion object { + const val BASE_URL = "https://api.github.com/" + const val REQUEST_TIMEOUT_DURATION = 10 + const val DEBUG = true + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/git_icon.png b/app/src/main/res/drawable/git_icon.png new file mode 100644 index 0000000..6a947de Binary files /dev/null and b/app/src/main/res/drawable/git_icon.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..64baac0 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_repo_list.xml b/app/src/main/res/layout/fragment_repo_list.xml new file mode 100644 index 0000000..4693a85 --- /dev/null +++ b/app/src/main/res/layout/fragment_repo_list.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_repo_list_item.xml b/app/src/main/res/layout/view_repo_list_item.xml new file mode 100644 index 0000000..a9ce5f8 --- /dev/null +++ b/app/src/main/res/layout/view_repo_list_item.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..6182538 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..4e3e6d0 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008000 + #005e00 + #008000 + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..5644d09 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + 20dp + + 18sp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..94fa0b2 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Trending Git + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..083c770 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,19 @@ + + + + + + + +