Skip to content

Commit

Permalink
Code review [#25] P5 - Custom view (MaterialBanner) instead of Snackbar
Browse files Browse the repository at this point in the history
  • Loading branch information
E-D-W-I-N committed May 15, 2021
1 parent 3a0893a commit ab8587e
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.edwin.weatherapp.presentation.customView

import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.Transformation
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.edwin.weatherapp.R
import com.edwin.weatherapp.databinding.MaterialBannerBinding
import com.google.android.material.color.MaterialColors

class MaterialBanner @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

private var _contentText: String? = null
private var _leftButtonText: String? = null
private var _rightButtonText: String? = null
private var _iconDrawableRes: Drawable? = null
private var binding: MaterialBannerBinding

var contentText: String?
get() = _contentText
set(value) {
_contentText = value
binding.contentTextView.text = value
}

var leftButtonText: String?
get() = _leftButtonText
set(value) {
_leftButtonText = value
binding.leftButton.text = value
}

var rightButtonText: String?
get() = _rightButtonText
set(value) {
_rightButtonText = value
binding.rightButton.text = value
}

var iconDrawableRes: Drawable?
get() = _iconDrawableRes
set(value) {
_iconDrawableRes = value
binding.contentIconView.setImageDrawable(value)
binding.contentIconView.visibility = View.VISIBLE
}

init {
val view = inflate(context, R.layout.material_banner, this)
binding = MaterialBannerBinding.bind(view)

val typedArray = context.obtainStyledAttributes(
attrs, R.styleable.MaterialBanner, 0, 0
)

contentText = typedArray.getString(
R.styleable.MaterialBanner_contentText
)

leftButtonText = typedArray.getString(
R.styleable.MaterialBanner_leftButtonText
)

rightButtonText = typedArray.getString(
R.styleable.MaterialBanner_rightButtonText
)

iconDrawableRes = typedArray.getDrawable(
R.styleable.MaterialBanner_icon
)

binding.bannerLayout.setBackgroundColor(
typedArray.getColor(
R.styleable.MaterialBanner_bannerBackgroundColor,
MaterialColors.getColor(view, R.attr.colorPrimary)
)
)

binding.contentTextView.setTextColor(
typedArray.getColor(
R.styleable.MaterialBanner_contentTextColor,
ContextCompat.getColor(context, R.color.blue)
)
)

binding.leftButton.setTextColor(
typedArray.getColor(
R.styleable.MaterialBanner_buttonsTextColor,
ContextCompat.getColor(context, R.color.blue)
)
)
binding.rightButton.setTextColor(
typedArray.getColor(
R.styleable.MaterialBanner_buttonsTextColor,
ContextCompat.getColor(context, R.color.blue)
)
)

typedArray.recycle()
}


fun showBanner(
@StringRes message: Int?,
@DrawableRes icon: Int?,
@StringRes leftBtnText: Int?,
@StringRes rightBtnText: Int?,
leftButtonAction: MaterialBanner.() -> Unit,
rightButtonAction: MaterialBanner.() -> Unit
) {
contentText = message?.let { context.getString(it) }
iconDrawableRes = icon?.let { ContextCompat.getDrawable(context, it) }
leftButtonText = leftBtnText?.let { context.getString(it) }
rightButtonText = rightBtnText?.let { context.getString(it) }
this.setLeftButtonAction { leftButtonAction() }
this.setRightButtonAction { rightButtonAction() }
this.expand()
}

fun dismiss() = this.collapse()

fun setLeftButtonAction(action: () -> Unit) = binding.leftButton.setOnClickListener {
action()
}

fun setRightButtonAction(action: () -> Unit) = binding.rightButton.setOnClickListener {
action()
}

private fun View.expand() {
this@expand.measure(
LayoutParams.MATCH_CONSTRAINT,
LayoutParams.WRAP_CONTENT
)
val targetHeight = this@expand.measuredHeight

this@expand.layoutParams.height = 0
this@expand.visibility = View.VISIBLE
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
this@expand.layoutParams.height = if (interpolatedTime == 1f)
ViewGroup.LayoutParams.WRAP_CONTENT
else
(targetHeight * interpolatedTime).toInt()
this@expand.requestLayout()
}

override fun willChangeBounds(): Boolean = true
}

animation.duration =
(targetHeight / this@expand.context.resources.displayMetrics.density).toInt().toLong()
this@expand.startAnimation(animation)
}

private fun View.collapse() {
val initialHeight = this.measuredHeight

val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
if (interpolatedTime == 1f) {
this@collapse.visibility = View.GONE
} else {
this@collapse.layoutParams.height =
initialHeight - (initialHeight * interpolatedTime).toInt()
this@collapse.requestLayout()
}
}

override fun willChangeBounds(): Boolean = true
}

animation.duration =
(initialHeight / this.context.resources.displayMetrics.density).toInt().toLong()
this.startAnimation(animation)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ class MapFragment : Fragment(R.layout.map_fragment), OnMapReadyCallback {
when (state) {
is MapViewModel.MapUiState.Loading -> binding.progressBar.visibility = View.VISIBLE
is MapViewModel.MapUiState.CurrentLocationLoaded -> {
binding.progressBar.visibility = View.INVISIBLE
binding.progressBar.visibility = View.GONE
val latLng = LatLng(state.location.latitude, state.location.longitude)
moveCameraToCurrentLocation(latLng)
}
is MapViewModel.MapUiState.AddressLoaded -> {
binding.progressBar.visibility = View.INVISIBLE
binding.progressBar.visibility = View.GONE
setupShowWeatherWindow(state.address)
}
is MapViewModel.MapUiState.Error -> binding.progressBar.visibility = View.INVISIBLE
is MapViewModel.MapUiState.Error -> binding.progressBar.visibility = View.GONE
else -> Unit
}
}.launchIn(viewLifecycleOwner.lifecycleScope)
Expand All @@ -90,9 +90,14 @@ class MapFragment : Fragment(R.layout.map_fragment), OnMapReadyCallback {
getString(R.string.no_city_error_text)
)
is MapException.NoLastLocation -> {
showSnackbar(getString(R.string.current_location_error_text)) {
action(R.string.action_retry) { viewModel.getFusedLocation() }
}
binding.banner.showBanner(
R.string.current_location_error_text,
R.drawable.ic_location_off,
R.string.action_dismiss,
R.string.action_retry,
{ binding.banner.dismiss() },
{ viewModel.getFusedLocation() }
)
}
}
}
Expand All @@ -102,7 +107,6 @@ class MapFragment : Fragment(R.layout.map_fragment), OnMapReadyCallback {

override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
checkPermissions()
map.setOnMapClickListener { latLng ->
map.clear()
map.addMarker {
Expand Down Expand Up @@ -182,6 +186,11 @@ class MapFragment : Fragment(R.layout.map_fragment), OnMapReadyCallback {
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkPermissions()
}

override fun onStart() {
super.onStart()
binding.mapView.onStart()
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_location_off.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector android:height="24dp"
android:tint="#4A90E2"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@android:color/white"
android:pathData="M12,6.5c1.38,0 2.5,1.12 2.5,2.5 0,0.74 -0.33,1.39 -0.83,1.85l3.63,3.63c0.98,-1.86 1.7,-3.8 1.7,-5.48 0,-3.87 -3.13,-7 -7,-7 -1.98,0 -3.76,0.83 -5.04,2.15l3.19,3.19c0.46,-0.52 1.11,-0.84 1.85,-0.84zM16.37,16.1l-4.63,-4.63 -0.11,-0.11L3.27,3 2,4.27l3.18,3.18C5.07,7.95 5,8.47 5,9c0,5.25 7,13 7,13s1.67,-1.85 3.38,-4.35L18.73,21 20,19.73l-3.63,-3.63z" />
</vector>
33 changes: 22 additions & 11 deletions app/src/main/res/layout/map_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,37 @@
android:layout_height="match_parent"
tools:context=".presentation.map.MapFragment">

<com.google.android.gms.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
android:visibility="gone"
app:indicatorColor="@color/blue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.edwin.weatherapp.presentation.customView.MaterialBanner
android:id="@+id/banner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progressBar"
app:layout_constraintVertical_bias="0.0" />

<com.google.android.gms.maps.MapView
android:id="@+id/mapView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/banner" />

<com.google.android.material.card.MaterialCardView
android:id="@+id/show_weather_window"
android:layout_width="match_parent"
Expand Down
60 changes: 60 additions & 0 deletions app/src/main/res/layout/material_banner.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bannerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<ImageView
android:id="@+id/contentIconView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:contentDescription="@string/banner_image"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_location_off"
tools:visibility="visible" />

<TextView
android:id="@+id/contentTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/blue"
tools:text="Test text of banner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/contentIconView"
app:layout_constraintStart_toEndOf="@id/contentIconView" />

<com.google.android.material.button.MaterialButton
android:id="@+id/leftButton"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textColor="@color/blue"
app:layout_constraintBottom_toBottomOf="@+id/rightButton"
app:layout_constraintEnd_toStartOf="@+id/rightButton"
app:layout_constraintTop_toTopOf="@+id/rightButton"
app:rippleColor="@color/blue"
tools:text="Dismiss" />

<com.google.android.material.button.MaterialButton
android:id="@+id/rightButton"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:textColor="@color/blue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/contentTextView"
app:rippleColor="@color/blue"
tools:text="Retry" />

</androidx.constraintlayout.widget.ConstraintLayout>
13 changes: 13 additions & 0 deletions app/src/main/res/values/attrs_banner.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<resources>
<declare-styleable name="MaterialBanner">
<attr name="contentText" format="string|reference" />
<attr name="leftButtonText" format="string|reference" />
<attr name="rightButtonText" format="string|reference" />
<attr name="icon" format="reference" />

<attr name="contentTextColor" format="color" />
<attr name="buttonsTextColor" format="color" />
<attr name="bannerBackgroundColor" format="color" />

</declare-styleable>
</resources>
Loading

0 comments on commit ab8587e

Please sign in to comment.