diff --git a/README.md b/README.md index e9753cf..fffb953 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # trending-git Trending Git App + +- MVVM (KOTLIN) +- ViewModel +- LiveData +- Navigation Component +- Data binding + +Screen Shots + +![alt text](screenshot/s1.png) +![alt text](screenshot/s2.png) +![alt text](screenshot/s3.png) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index f401ea6..d49fceb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { compileSdkVersion 28 defaultConfig { applicationId "com.ankit.trendinggit" - minSdkVersion 16 + minSdkVersion 17 targetSdkVersion 28 versionCode 1 versionName "1.0" @@ -50,6 +50,9 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' + // Picasso + implementation 'com.squareup.picasso:picasso:2.71828' + // Databinding compiler kapt 'com.android.databinding:compiler:3.2.0-alpha10' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ce38f9..c1dc4dc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ Unit) { - ApiClient.instance.getRepo().enqueue(object :Callback { + 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) @@ -27,8 +27,9 @@ class RepoRepository { companion object { private var INSTANCE: RepoRepository? = null - fun getInstance() = INSTANCE ?: RepoRepository().also { - INSTANCE = it - } + fun getInstance() = INSTANCE + ?: RepoRepository().also { + INSTANCE = it + } } } \ 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 index 5e61c31..d8b8092 100644 --- a/app/src/main/java/com/ankit/trendinggit/model/api/ApiService.kt +++ b/app/src/main/java/com/ankit/trendinggit/model/api/ApiService.kt @@ -8,5 +8,5 @@ import retrofit2.http.Query interface ApiService { @GET("search/repositories") - fun getRepo(@Query("q") search: String = "trending"): Call + fun getRepo(@Query("q") search: String = "trending", @Query("sort") sort: String = "stars"): Call } \ 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 index af3796e..ee75297 100644 --- 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 @@ -1,16 +1,30 @@ package com.ankit.trendinggit.view.adapter.viewHolders import androidx.databinding.ViewDataBinding +import androidx.navigation.findNavController import androidx.recyclerview.widget.RecyclerView import com.ankit.trendinggit.BR +import com.ankit.trendinggit.R import com.ankit.trendinggit.model.Item import com.ankit.trendinggit.view.ui.repolist.RepoListViewModel +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.view_repo_list_item.view.* +import org.jetbrains.anko.bundleOf +import org.jetbrains.anko.sdk25.coroutines.onClick class RepoListViewHolder constructor(private val dataBinding: ViewDataBinding, private val repoListViewModel: RepoListViewModel) : RecyclerView.ViewHolder(dataBinding.root) { + val avatarImage = itemView.item_avatar fun setup(itemData: Item) { dataBinding.setVariable(BR.itemData, itemData) dataBinding.executePendingBindings() + + Picasso.get().load(itemData.owner.avatar_url).into(avatarImage); + + itemView.onClick { + val bundle = bundleOf("url" to itemData.html_url) + itemView.findNavController().navigate(R.id.action_repoListFragment_to_repoDetailFragment, bundle) + } } } \ 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 index c5323c4..1575f82 100644 --- a/app/src/main/java/com/ankit/trendinggit/view/base/BaseViewModel.kt +++ b/app/src/main/java/com/ankit/trendinggit/view/base/BaseViewModel.kt @@ -11,6 +11,4 @@ open class BaseViewModel : ViewModel() { 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/repodetail/RepoDetailFragment.kt b/app/src/main/java/com/ankit/trendinggit/view/ui/repodetail/RepoDetailFragment.kt new file mode 100644 index 0000000..a2eb78e --- /dev/null +++ b/app/src/main/java/com/ankit/trendinggit/view/ui/repodetail/RepoDetailFragment.kt @@ -0,0 +1,82 @@ +package com.ankit.trendinggit.view.ui.repodetail + +import android.graphics.Bitmap +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.fragment.app.Fragment +import com.ankit.trendinggit.R +import kotlinx.android.synthetic.main.fragment_repo_detail.* +import org.jetbrains.anko.sdk25.coroutines.onClick + + +class RepoDetailFragment : Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_repo_detail, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val url = RepoDetailFragmentArgs.fromBundle(arguments).url + + setupWebView() + setClickListeners() + + repo_web_view.loadUrl(url) + } + + private fun setClickListeners() { + repo_back_button.onClick { + repo_web_view.goBack() + } + + repo_forward_button.onClick { + repo_web_view.goForward() + } + + repo_refresh_button.onClick { + repo_web_view.reload() + } + } + + private fun setupWebView() { + repo_web_view.setInitialScale(1) + val webSettings = repo_web_view.settings + webSettings.setAppCacheEnabled(false) + webSettings.builtInZoomControls = true + webSettings.displayZoomControls = false + webSettings.javaScriptEnabled = true + webSettings.useWideViewPort = true + webSettings.domStorageEnabled = true + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW; + } + + repo_web_view.webViewClient = object : WebViewClient() { + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + if (repo_back_button != null && repo_forward_button != null && repo_web_view != null && repo_progress_view != null) { + repo_back_button.isEnabled = repo_web_view.canGoBack() + repo_forward_button.isEnabled = repo_web_view.canGoForward() + repo_progress_view.visibility = View.VISIBLE + } + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + if (repo_back_button != null && repo_forward_button != null && repo_web_view != null && repo_progress_view != null) { + repo_back_button.isEnabled = repo_web_view.canGoBack() + repo_forward_button.isEnabled = repo_web_view.canGoForward() + repo_progress_view.visibility = View.GONE + } + } + } + } +} \ 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 index c456829..b8dab3d 100644 --- 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 @@ -12,6 +12,7 @@ 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.* +import org.jetbrains.anko.longToast class RepoListFragment : Fragment() { @@ -33,10 +34,15 @@ class RepoListFragment : Fragment() { setupAdapter() setupObservers() } + private fun setupObservers() { viewDataBinding.viewmodel?.repoListLive?.observe(viewLifecycleOwner, Observer { adapter.updateRepoList(it) }) + + viewDataBinding.viewmodel?.toastMessage?.observe(viewLifecycleOwner, Observer { + activity?.longToast(it) + }) } private fun setupAdapter() { 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 index 91056e4..7f0dac3 100644 --- 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 @@ -2,7 +2,7 @@ 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.model.RepoRepository import com.ankit.trendinggit.view.base.BaseViewModel class RepoListViewModel : BaseViewModel() { @@ -12,7 +12,7 @@ class RepoListViewModel : BaseViewModel() { dataLoading.value = true RepoRepository.getInstance().getRepoList { isSuccess, response -> dataLoading.value = false - if(isSuccess) { + if (isSuccess) { repoListLive.value = response?.items empty.value = false } else { diff --git a/app/src/main/res/drawable/ic_arrow_backward.xml b/app/src/main/res/drawable/ic_arrow_backward.xml new file mode 100644 index 0000000..81600ea --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_backward.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml new file mode 100644 index 0000000..c0c90d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_fork.png b/app/src/main/res/drawable/ic_fork.png new file mode 100644 index 0000000..1f12132 Binary files /dev/null and b/app/src/main/res/drawable/ic_fork.png differ diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..84720c3 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_star_black.xml b/app/src/main/res/drawable/ic_star_black.xml new file mode 100644 index 0000000..a87ca09 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_repo_detail.xml b/app/src/main/res/layout/fragment_repo_detail.xml new file mode 100644 index 0000000..d021dbb --- /dev/null +++ b/app/src/main/res/layout/fragment_repo_detail.xml @@ -0,0 +1,62 @@ + + + + + + + +