From f94d336eca428506c7913d0a363cc09ab8bc6ded Mon Sep 17 00:00:00 2001 From: ankitb Date: Mon, 10 Sep 2018 23:36:09 +0530 Subject: [PATCH] - RepoDetail Fragment created - Other resource files updated - App screenshots added - Readme updated --- README.md | 12 +++ app/build.gradle | 5 +- app/src/main/AndroidManifest.xml | 1 + .../model/{api => }/RepoRepository.kt | 15 +-- .../ankit/trendinggit/model/api/ApiService.kt | 2 +- .../adapter/viewHolders/RepoListViewHolder.kt | 14 +++ .../trendinggit/view/base/BaseViewModel.kt | 2 - .../view/ui/repodetail/RepoDetailFragment.kt | 82 +++++++++++++++ .../view/ui/repolist/RepoListFragment.kt | 6 ++ .../view/ui/repolist/RepoListViewModel.kt | 4 +- .../main/res/drawable/ic_arrow_backward.xml | 5 + .../main/res/drawable/ic_arrow_forward.xml | 5 + app/src/main/res/drawable/ic_fork.png | Bin 0 -> 4091 bytes app/src/main/res/drawable/ic_refresh.xml | 5 + app/src/main/res/drawable/ic_star_black.xml | 9 ++ .../main/res/layout/fragment_repo_detail.xml | 62 ++++++++++++ .../main/res/layout/view_repo_list_item.xml | 94 +++++++++++++++++- app/src/main/res/navigation/nav_graph.xml | 15 ++- app/src/main/res/values/dimens.xml | 9 ++ screenshot/s1.png | Bin 0 -> 78139 bytes screenshot/s2.png | Bin 0 -> 13454 bytes screenshot/s3.png | Bin 0 -> 76107 bytes 22 files changed, 331 insertions(+), 16 deletions(-) rename app/src/main/java/com/ankit/trendinggit/model/{api => }/RepoRepository.kt (71%) create mode 100644 app/src/main/java/com/ankit/trendinggit/view/ui/repodetail/RepoDetailFragment.kt create mode 100644 app/src/main/res/drawable/ic_arrow_backward.xml create mode 100644 app/src/main/res/drawable/ic_arrow_forward.xml create mode 100644 app/src/main/res/drawable/ic_fork.png create mode 100644 app/src/main/res/drawable/ic_refresh.xml create mode 100644 app/src/main/res/drawable/ic_star_black.xml create mode 100644 app/src/main/res/layout/fragment_repo_detail.xml create mode 100644 screenshot/s1.png create mode 100644 screenshot/s2.png create mode 100644 screenshot/s3.png 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 0000000000000000000000000000000000000000..1f121325067f5c2a5037e5fe8cc12608aa96c781 GIT binary patch literal 4091 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 nFv + + 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 @@ + + + + + + + +