From 542f4b40e8baf248964993ea6881c933b08ba965 Mon Sep 17 00:00:00 2001 From: Rafael Chagas Date: Wed, 19 Oct 2022 00:13:11 +0200 Subject: [PATCH] Use View Binding Signed-off-by: Rafael Chagas --- build.gradle | 1 + library/build.gradle | 6 +- library/src/main/AndroidManifest.xml | 2 +- .../pingplacepicker/PingPlacePicker.kt | 2 +- .../repository/googlemaps/CustomPlace.kt | 2 +- .../ui/activity/BaseActivity.kt | 35 ++++++ .../ui/{ => activity}/PlacePickerActivity.kt | 71 ++++++------ .../ui/{ => adapter}/PlacePickerAdapter.kt | 28 ++--- .../PlaceConfirmDialogFragment.kt | 108 ++++++++++-------- 9 files changed, 150 insertions(+), 105 deletions(-) create mode 100644 library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/BaseActivity.kt rename library/src/main/java/com/rtchagas/pingplacepicker/ui/{ => activity}/PlacePickerActivity.kt (92%) rename library/src/main/java/com/rtchagas/pingplacepicker/ui/{ => adapter}/PlacePickerAdapter.kt (62%) rename library/src/main/java/com/rtchagas/pingplacepicker/ui/{ => fragment}/PlaceConfirmDialogFragment.kt (72%) diff --git a/build.gradle b/build.gradle index ebf8957..8272651 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { google() mavenCentral() } + dependencies { classpath 'com.android.tools.build:gradle:7.2.2' classpath 'com.google.gms:google-services:4.3.14' diff --git a/library/build.gradle b/library/build.gradle index 84103dc..99c7243 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' apply plugin: 'maven-publish' android { @@ -18,6 +18,10 @@ android { freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" } + buildFeatures { + viewBinding true + } + defaultConfig { minSdkVersion 19 targetSdkVersion 32 diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 3b0c287..1a07c39 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -14,7 +14,7 @@ diff --git a/library/src/main/java/com/rtchagas/pingplacepicker/PingPlacePicker.kt b/library/src/main/java/com/rtchagas/pingplacepicker/PingPlacePicker.kt index 015c7b9..e9f8d01 100644 --- a/library/src/main/java/com/rtchagas/pingplacepicker/PingPlacePicker.kt +++ b/library/src/main/java/com/rtchagas/pingplacepicker/PingPlacePicker.kt @@ -8,7 +8,7 @@ import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.maps.model.LatLng import com.google.android.libraries.places.api.model.Place import com.rtchagas.pingplacepicker.inject.PingKoinContext -import com.rtchagas.pingplacepicker.ui.PlacePickerActivity +import com.rtchagas.pingplacepicker.ui.activity.PlacePickerActivity object PingPlacePicker { diff --git a/library/src/main/java/com/rtchagas/pingplacepicker/repository/googlemaps/CustomPlace.kt b/library/src/main/java/com/rtchagas/pingplacepicker/repository/googlemaps/CustomPlace.kt index 99049ba..b3a523d 100644 --- a/library/src/main/java/com/rtchagas/pingplacepicker/repository/googlemaps/CustomPlace.kt +++ b/library/src/main/java/com/rtchagas/pingplacepicker/repository/googlemaps/CustomPlace.kt @@ -4,7 +4,7 @@ import android.net.Uri import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds import com.google.android.libraries.places.api.model.* -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize internal class CustomPlace( diff --git a/library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/BaseActivity.kt b/library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/BaseActivity.kt new file mode 100644 index 0000000..651fca5 --- /dev/null +++ b/library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/BaseActivity.kt @@ -0,0 +1,35 @@ +package com.rtchagas.pingplacepicker.ui.activity + +import android.os.Bundle +import android.view.LayoutInflater +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding + +typealias ActivityInflater = (LayoutInflater) -> T + +abstract class BaseActivity( + private val inflate: ActivityInflater +) : AppCompatActivity() { + + private var _binding: T? = null + + /** + * The view binding for this fragment. + * + * [https://developer.android.com/topic/libraries/view-binding] + * + * This property is only valid between [onCreate] and [onDestroy]. + */ + val binding: T get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = inflate(layoutInflater) + setContentView(binding.root) + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } +} diff --git a/library/src/main/java/com/rtchagas/pingplacepicker/ui/PlacePickerActivity.kt b/library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/PlacePickerActivity.kt similarity index 92% rename from library/src/main/java/com/rtchagas/pingplacepicker/ui/PlacePickerActivity.kt rename to library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/PlacePickerActivity.kt index 8708817..2683223 100644 --- a/library/src/main/java/com/rtchagas/pingplacepicker/ui/PlacePickerActivity.kt +++ b/library/src/main/java/com/rtchagas/pingplacepicker/ui/activity/PlacePickerActivity.kt @@ -1,4 +1,4 @@ -package com.rtchagas.pingplacepicker.ui +package com.rtchagas.pingplacepicker.ui.activity import android.annotation.SuppressLint import android.app.Activity @@ -11,7 +11,6 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View -import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat @@ -39,18 +38,23 @@ import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.single.BasePermissionListener import com.rtchagas.pingplacepicker.PingPlacePicker import com.rtchagas.pingplacepicker.R +import com.rtchagas.pingplacepicker.databinding.ActivityPlacePickerBinding import com.rtchagas.pingplacepicker.helper.PermissionsHelper import com.rtchagas.pingplacepicker.inject.PingKoinComponent +import com.rtchagas.pingplacepicker.ui.UiUtils +import com.rtchagas.pingplacepicker.ui.adapter.PlacePickerAdapter +import com.rtchagas.pingplacepicker.ui.fragment.PlaceConfirmDialogFragment +import com.rtchagas.pingplacepicker.ui.onclick import com.rtchagas.pingplacepicker.viewmodel.PlacePickerViewModel import com.rtchagas.pingplacepicker.viewmodel.Resource import io.reactivex.disposables.CompositeDisposable -import kotlinx.android.synthetic.main.activity_place_picker.* import kotlinx.coroutines.delay import org.jetbrains.anko.toast import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.math.abs -internal class PlacePickerActivity : AppCompatActivity(), +internal class PlacePickerActivity : + BaseActivity(ActivityPlacePickerBinding::inflate), PingKoinComponent, OnMapReadyCallback, GoogleMap.OnMarkerClickListener, @@ -97,10 +101,9 @@ internal class PlacePickerActivity : AppCompatActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_place_picker) // Configure the toolbar - setSupportActionBar(toolbar) + setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) // Check whether a pre-defined location was set. @@ -208,13 +211,16 @@ internal class PlacePickerActivity : AppCompatActivity(), UiUtils.getColorAttr(this, R.attr.colorPrimarySurface), resources.getDimension(R.dimen.material_elevation_app_bar) ) - collapsingToolbarLayout.setContentScrimColor(scrimColor) // Set the correct elevation to the content container val containerColor = elevationOverlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded( resources.getDimension(R.dimen.material_elevation_app_bar) ) - mapContainer.setBackgroundColor(containerColor) + + with(binding) { + collapsingToolbarLayout.setContentScrimColor(scrimColor) + mapContainer.setBackgroundColor(containerColor) + } } private fun bindPlaces(places: List) { @@ -227,7 +233,7 @@ internal class PlacePickerActivity : AppCompatActivity(), placeAdapter?.swapData(places) } - rvNearbyPlaces.adapter = placeAdapter + binding.rvNearbyPlaces.adapter = placeAdapter // Bind to the map @@ -350,53 +356,44 @@ internal class PlacePickerActivity : AppCompatActivity(), private fun handlePlaceByLocation(result: Resource) { + binding.pbLoading.hide() + when (result.status) { - Resource.Status.LOADING -> { - pbLoading.show() - } - Resource.Status.SUCCESS -> { + Resource.Status.LOADING -> + binding.pbLoading.show() + Resource.Status.SUCCESS -> result.data?.run { showConfirmPlacePopup(this) } - pbLoading.hide() - } - Resource.Status.ERROR -> { + Resource.Status.ERROR -> toast(R.string.picker_load_this_place_error) - pbLoading.hide() - } - Resource.Status.NO_DATA -> { + Resource.Status.NO_DATA -> Log.d(TAG, "No places data found...") - } } - } private fun handlePlacesLoaded(result: Resource>) { + binding.pbLoading.hide() + when (result.status) { - Resource.Status.LOADING -> { - pbLoading.show() - } - Resource.Status.SUCCESS -> { + Resource.Status.LOADING -> + binding.pbLoading.show() + Resource.Status.SUCCESS -> bindPlaces((result.data ?: listOf())) - pbLoading.hide() - } - Resource.Status.ERROR -> { + Resource.Status.ERROR -> toast(R.string.picker_load_places_error) - pbLoading.hide() - } - Resource.Status.NO_DATA -> { + Resource.Status.NO_DATA -> Log.d(TAG, "No places data found...") - } } } - private fun initializeUi() { + private fun initializeUi() = with(binding) { // Some material components still don't support setting the correct // elevation for dark themes, so we should handle that adjustElevationOverlayColors() // Initialize the recycler view - rvNearbyPlaces.layoutManager = LinearLayoutManager(this) + rvNearbyPlaces.layoutManager = LinearLayoutManager(this@PlacePickerActivity) // Bind the click listeners disposables.addAll( @@ -541,7 +538,7 @@ internal class PlacePickerActivity : AppCompatActivity(), // Location is not available. Give up... setDefaultLocation() Snackbar - .make(coordinator, R.string.picker_location_unavailable, Snackbar.LENGTH_INDEFINITE) + .make(binding.root, R.string.picker_location_unavailable, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.places_try_again) { getDeviceLocation(animate) } .show() } @@ -600,9 +597,9 @@ internal class PlacePickerActivity : AppCompatActivity(), if (isLocationPermissionGranted) { it.isMyLocationEnabled = true - btnMyLocation.visibility = View.VISIBLE + binding.btnMyLocation.visibility = View.VISIBLE } else { - btnMyLocation.visibility = View.GONE + binding.btnMyLocation.visibility = View.GONE it.isMyLocationEnabled = false } } diff --git a/library/src/main/java/com/rtchagas/pingplacepicker/ui/PlacePickerAdapter.kt b/library/src/main/java/com/rtchagas/pingplacepicker/ui/adapter/PlacePickerAdapter.kt similarity index 62% rename from library/src/main/java/com/rtchagas/pingplacepicker/ui/PlacePickerAdapter.kt rename to library/src/main/java/com/rtchagas/pingplacepicker/ui/adapter/PlacePickerAdapter.kt index 988bd8f..6856911 100644 --- a/library/src/main/java/com/rtchagas/pingplacepicker/ui/PlacePickerAdapter.kt +++ b/library/src/main/java/com/rtchagas/pingplacepicker/ui/adapter/PlacePickerAdapter.kt @@ -1,12 +1,11 @@ -package com.rtchagas.pingplacepicker.ui +package com.rtchagas.pingplacepicker.ui.adapter import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.google.android.libraries.places.api.model.Place -import com.rtchagas.pingplacepicker.R -import kotlinx.android.synthetic.main.item_place.view.* +import com.rtchagas.pingplacepicker.databinding.ItemPlaceBinding +import com.rtchagas.pingplacepicker.ui.UiUtils internal class PlacePickerAdapter( private var placeList: List, @@ -14,32 +13,30 @@ internal class PlacePickerAdapter( ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaceViewHolder { - - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_place, parent, false) - - return PlaceViewHolder(view) + val inflater = LayoutInflater.from(parent.context) + val binding = ItemPlaceBinding.inflate(inflater, parent, false) + return PlaceViewHolder(binding) } override fun onBindViewHolder(holder: PlaceViewHolder, position: Int) { holder.bind(placeList[position], clickListener) } - override fun getItemCount(): Int { - return placeList.size - } + override fun getItemCount(): Int = + placeList.size fun swapData(newPlaceList: List) { placeList = newPlaceList notifyDataSetChanged() } - inner class PlaceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class PlaceViewHolder(private val binding: ItemPlaceBinding) : + RecyclerView.ViewHolder(binding.root) { fun bind(place: Place, listener: (Place) -> Unit) { - with(itemView) { - setOnClickListener { listener(place) } + with(binding) { + root.setOnClickListener { listener(place) } ivPlaceType.setImageResource(UiUtils.getPlaceDrawableRes(itemView.context, place)) tvPlaceName.text = place.name tvPlaceAddress.text = place.address @@ -47,4 +44,3 @@ internal class PlacePickerAdapter( } } } - diff --git a/library/src/main/java/com/rtchagas/pingplacepicker/ui/PlaceConfirmDialogFragment.kt b/library/src/main/java/com/rtchagas/pingplacepicker/ui/fragment/PlaceConfirmDialogFragment.kt similarity index 72% rename from library/src/main/java/com/rtchagas/pingplacepicker/ui/PlaceConfirmDialogFragment.kt rename to library/src/main/java/com/rtchagas/pingplacepicker/ui/fragment/PlaceConfirmDialogFragment.kt index b594076..302f9f9 100644 --- a/library/src/main/java/com/rtchagas/pingplacepicker/ui/PlaceConfirmDialogFragment.kt +++ b/library/src/main/java/com/rtchagas/pingplacepicker/ui/fragment/PlaceConfirmDialogFragment.kt @@ -1,4 +1,4 @@ -package com.rtchagas.pingplacepicker.ui +package com.rtchagas.pingplacepicker.ui.fragment import android.annotation.SuppressLint import android.app.Dialog @@ -8,7 +8,6 @@ import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDialogFragment import androidx.core.view.isVisible @@ -17,45 +16,31 @@ import com.google.android.libraries.places.api.model.Place import com.rtchagas.pingplacepicker.Config import com.rtchagas.pingplacepicker.PingPlacePicker import com.rtchagas.pingplacepicker.R +import com.rtchagas.pingplacepicker.databinding.FragmentDialogPlaceConfirmBinding import com.rtchagas.pingplacepicker.helper.UrlSignerHelper import com.rtchagas.pingplacepicker.inject.PingKoinComponent +import com.rtchagas.pingplacepicker.ui.UiUtils import com.rtchagas.pingplacepicker.viewmodel.PlaceConfirmDialogViewModel import com.rtchagas.pingplacepicker.viewmodel.Resource import com.squareup.picasso.Callback import com.squareup.picasso.Picasso -import kotlinx.android.synthetic.main.fragment_dialog_place_confirm.view.* import org.koin.androidx.viewmodel.ext.android.viewModel import java.util.* internal class PlaceConfirmDialogFragment : AppCompatDialogFragment(), PingKoinComponent { - companion object { - - private const val TAG = "Ping#PlaceConfirmDialog" - private const val ARG_PLACE = "arg_place" - - fun newInstance( - place: Place, - listener: OnPlaceConfirmedListener - ): PlaceConfirmDialogFragment { - - val args = Bundle() - args.putParcelable(ARG_PLACE, place) + private var _binding: FragmentDialogPlaceConfirmBinding? = null - return PlaceConfirmDialogFragment().apply { - arguments = args - confirmListener = listener - } - } - } - - var confirmListener: OnPlaceConfirmedListener? = null + private val binding: FragmentDialogPlaceConfirmBinding + get() = _binding!! private val viewModel: PlaceConfirmDialogViewModel by viewModel() private lateinit var place: Place + var confirmListener: OnPlaceConfirmedListener? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -87,59 +72,66 @@ internal class PlaceConfirmDialogFragment : AppCompatDialogFragment(), PingKoinC return builder.create() } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + @SuppressLint("InflateParams") private fun getContentView(context: Context): View { + _binding = FragmentDialogPlaceConfirmBinding.inflate(LayoutInflater.from(context)) + initializeUi() + return binding.root + } - val content = LayoutInflater.from(context) - .inflate(R.layout.fragment_dialog_place_confirm, null) + private fun initializeUi() = with(binding) { if (place.name.isNullOrEmpty()) { - content.tvPlaceName.isVisible = false + tvPlaceName.isVisible = false } else { - content.tvPlaceName.text = place.name + tvPlaceName.text = place.name } - content.tvPlaceAddress.text = place.address + tvPlaceAddress.text = place.address - fetchPlaceMap(content) - fetchPlacePhoto(content) + fetchPlaceMap() + fetchPlacePhoto() - return content } - private fun fetchPlaceMap(contentView: View) { + private fun fetchPlaceMap() { if (resources.getBoolean(R.bool.show_confirmation_map)) { + val staticMapUrl = getFinalMapUrl() - Picasso.get().load(staticMapUrl).into(contentView.ivPlaceMap, object : Callback { + + Picasso.get().load(staticMapUrl).into(binding.ivPlaceMap, object : Callback { override fun onSuccess() { - contentView.ivPlaceMap.visibility = View.VISIBLE + binding.ivPlaceMap.visibility = View.VISIBLE } override fun onError(e: Exception?) { Log.e(TAG, "Error loading map image", e) - contentView.ivPlaceMap.visibility = View.GONE + binding.ivPlaceMap.visibility = View.GONE } }) } else { - contentView.ivPlaceMap.visibility = View.GONE + binding.ivPlaceMap.visibility = View.GONE } } - private fun fetchPlacePhoto(contentView: View) { + private fun fetchPlacePhoto() { - val photoMetadatas = place.photoMetadatas + val photoMetadata = place.photoMetadatas?.firstOrNull() if (resources.getBoolean(R.bool.show_confirmation_photo) - && photoMetadatas != null - && photoMetadatas.isNotEmpty() + && (photoMetadata != null) ) { - val photoMetadata = photoMetadatas[0] viewModel.getPlacePhoto(photoMetadata) - .observe(this) { handlePlacePhotoLoaded(contentView, it) } + .observe(this) { handlePlacePhotoLoaded(it) } } else { - handlePlacePhotoLoaded(contentView, Resource.noData()) + handlePlacePhotoLoaded(Resource.noData()) } } @@ -165,13 +157,13 @@ internal class PlaceConfirmDialogFragment : AppCompatDialogFragment(), PingKoinC return mapUrl } - private fun handlePlacePhotoLoaded(contentView: View, result: Resource) { + private fun handlePlacePhotoLoaded(result: Resource) { if (result.status == Resource.Status.SUCCESS) { - TransitionManager.beginDelayedTransition(contentView as ViewGroup) - contentView.ivPlacePhoto.visibility = View.VISIBLE - contentView.ivPlacePhoto.setImageBitmap(result.data) + TransitionManager.beginDelayedTransition(binding.root) + binding.ivPlacePhoto.visibility = View.VISIBLE + binding.ivPlacePhoto.setImageBitmap(result.data) } else { - contentView.ivPlacePhoto.visibility = View.GONE + binding.ivPlacePhoto.visibility = View.GONE } } @@ -181,4 +173,24 @@ internal class PlaceConfirmDialogFragment : AppCompatDialogFragment(), PingKoinC interface OnPlaceConfirmedListener { fun onPlaceConfirmed(place: Place) } + + companion object { + + private const val TAG = "Ping#PlaceConfirmDialog" + private const val ARG_PLACE = "arg_place" + + fun newInstance( + place: Place, + listener: OnPlaceConfirmedListener + ): PlaceConfirmDialogFragment { + + val args = Bundle() + args.putParcelable(ARG_PLACE, place) + + return PlaceConfirmDialogFragment().apply { + arguments = args + confirmListener = listener + } + } + } }