Skip to content

Commit

Permalink
Merge pull request #190 from amardeshbd/feature/show-filter-fab-menu
Browse files Browse the repository at this point in the history
REFACTOR+ADD -  Show latest incidents option
  • Loading branch information
hossain-khan authored Jul 9, 2020
2 parents 28c49a2 + 83c9557 commit ef97840
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 47 deletions.
13 changes: 8 additions & 5 deletions android-app/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ android {
}
}


buildFeatures { // https://developer.android.com/studio/releases/gradle-plugin#buildFeatures
dataBinding = true
}

buildTypes {
release {
// https://developer.android.com/studio/build/shrink-code
Expand Down Expand Up @@ -85,6 +80,14 @@ android {
jvmTarget = '1.8'
}

buildFeatures { // https://developer.android.com/studio/releases/gradle-plugin#buildFeatures
dataBinding = true
}

androidExtensions { // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.android.extensions
features = ["parcelize"]
}

lintOptions { // https://developer.android.com/studio/write/lint.html
checkAllWarnings true
warningsAsErrors true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Analytics {
const val SCREEN_INCIDENT_LOCATION = "IncidentLocations"
const val SCREEN_INCIDENT_LIST_BY_DATE = "IncidentsByDate"
const val SCREEN_INCIDENT_LIST_BY_LOCATION = "IncidentsByLocation"
const val SCREEN_INCIDENT_LIST_MOST_RECENT = "IncidentsByRecent"
const val SCREEN_INCIDENT_DATE_FILTER = "FilterIncidentsByDate"
const val SCREEN_MORE_INFO = "MoreInformation"
const val SCREEN_ABOUT_APP = "AboutApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class BrutalityIncidentRepository @Inject constructor(
return incidentDao.getIncidentsByDate(timeStamp)
}

override fun getIncidentsRecentFirst(): LiveData<List<Incident>> {
return incidentDao.getIncidentsRecentFirst()
}

override fun getLocations(): LiveData<List<LocationIncidents>> {
return incidentDao.getUniqueStates()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ interface IncidentDao {
@Query("SELECT * FROM incidents WHERE DATE(date) = DATE(DATETIME(:timestamp, 'unixepoch')) ORDER BY name")
fun getIncidentsByDate(timestamp: Long): LiveData<List<Incident>>

@Query("SELECT * FROM incidents WHERE 1 ORDER BY date DESC")
fun getIncidentsRecentFirst(): LiveData<List<Incident>>

@Query("SELECT COUNT(id) FROM incidents")
suspend fun getTotalRecords(): Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface IncidentRepository {
fun getIncidents(): LiveData<List<Incident>>
fun getStateIncidents(state: String): LiveData<List<Incident>>
fun getIncidentsByDate(timeStamp: Long): LiveData<List<Incident>>
fun getIncidentsRecentFirst(): LiveData<List<Incident>>
fun getLocations(): LiveData<List<LocationIncidents>>
fun getTotalIncidentsOnDate(timeStamp: Long): LiveData<Int>
fun getIncidentDates(): LiveData<List<String>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.blacklivesmatter.policebrutality.config.PREF_KEY_SHARE_CAPABILITY_REM
import com.blacklivesmatter.policebrutality.data.IncidentRepository
import com.blacklivesmatter.policebrutality.data.model.Incident
import com.blacklivesmatter.policebrutality.ui.extensions.LiveEvent
import com.blacklivesmatter.policebrutality.ui.incident.arg.FilterType
import timber.log.Timber

class IncidentViewModel @ViewModelInject constructor(
Expand All @@ -38,9 +39,10 @@ class IncidentViewModel @ViewModelInject constructor(
}

fun setArgs(navArgs: IncidentsFragmentArgs) {
navArgs.stateName?.let { selectedSate(it) }
if (navArgs.timestamp != 0L) {
selectedTimestamp(navArgs.timestamp)
when (navArgs.filterArgs.type) {
FilterType.STATE -> selectedSate(navArgs.filterArgs.stateName!!)
FilterType.DATE -> selectedTimestamp(navArgs.filterArgs.timestamp!!)
FilterType.LATEST -> selectedMostRecentIncidents()
}

val isMessageShown = preferences.getBoolean(PREF_KEY_SHARE_CAPABILITY_REMINDER_SHOWN, false)
Expand Down Expand Up @@ -68,4 +70,11 @@ class IncidentViewModel @ViewModelInject constructor(
_incidents.value = it
}
}

private fun selectedMostRecentIncidents() {
_incidents.addSource(incidentRepository.getIncidentsRecentFirst()) {
Timber.d("Incidents Updated ")
_incidents.value = it
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import com.blacklivesmatter.policebrutality.R
import com.blacklivesmatter.policebrutality.data.model.Incident
import com.blacklivesmatter.policebrutality.databinding.ListItemIncidentCoreBinding
import com.blacklivesmatter.policebrutality.ui.common.DataBoundListAdapter
import com.blacklivesmatter.policebrutality.ui.incident.arg.FilterType

class IncidentsAdapter constructor(
private val isDateBasedIncidents: Boolean,
private val incidentFilterType: FilterType,
private val itemClickCallback: ((Incident) -> Unit)?,
private val linkClickCallback: ((String) -> Unit)? = null
) : DataBoundListAdapter<Incident, ListItemIncidentCoreBinding>(
Expand Down Expand Up @@ -42,7 +43,7 @@ class IncidentsAdapter constructor(

override fun bind(binding: ListItemIncidentCoreBinding, item: Incident) {
binding.incident = item
binding.isDateBased = isDateBasedIncidents
binding.shouldShowCityAndState = incidentFilterType != FilterType.STATE

val adapter = IncidentLinkAdapter(itemClickCallback = linkClickCallback)
binding.linksRecyclerView.layoutManager = LinearLayoutManager(binding.root.context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.blacklivesmatter.policebrutality.analytics.Analytics.Companion.CONTEN
import com.blacklivesmatter.policebrutality.data.model.Incident
import com.blacklivesmatter.policebrutality.databinding.FragmentIncidentsBinding
import com.blacklivesmatter.policebrutality.ui.extensions.observeKotlin
import com.blacklivesmatter.policebrutality.ui.incident.arg.FilterType
import com.blacklivesmatter.policebrutality.ui.util.IntentBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -44,7 +45,7 @@ class IncidentsFragment : Fragment() {
viewModel.setArgs(navArgs)

adapter = IncidentsAdapter(
isDateBasedIncidents = navArgs.isDateBased(),
incidentFilterType = navArgs.filterArgs.type,
itemClickCallback = { clickedIncident ->
Timber.d("Selected Incident: $clickedIncident")
analytics.logSelectItem(
Expand Down Expand Up @@ -95,12 +96,7 @@ class IncidentsFragment : Fragment() {

override fun onStart() {
super.onStart()
activity?.let {
analytics.logPageView(
it, if (navArgs.isDateBased()) Analytics.SCREEN_INCIDENT_LIST_BY_DATE
else Analytics.SCREEN_INCIDENT_LIST_BY_LOCATION
)
}
activity?.let { analytics.logPageView(it, navArgs.screenName()) }
}

private fun showIncidentDetailsForSharing(incident: Incident) {
Expand Down Expand Up @@ -133,9 +129,27 @@ class IncidentsFragment : Fragment() {
}
}

private fun IncidentsFragmentArgs.isDateBased(): Boolean = timestamp != 0L
private fun IncidentsFragmentArgs.titleResId(): Int =
if (isDateBased()) R.string.title_incidents_on_date else R.string.title_incidents_at_location
private fun IncidentsFragmentArgs.titleResId(): Int {
return when (filterArgs.type) {
FilterType.STATE -> R.string.title_incidents_at_location
FilterType.DATE -> R.string.title_incidents_on_date
FilterType.LATEST -> R.string.title_incidents_most_recent
}
}

private fun IncidentsFragmentArgs.titleText(): String {
return when (filterArgs.type) {
FilterType.STATE -> filterArgs.stateName!!
FilterType.DATE -> filterArgs.dateText!!
FilterType.LATEST -> ""
}
}

private fun IncidentsFragmentArgs.titleText(): String = if (isDateBased()) dateText!! else stateName!!
private fun IncidentsFragmentArgs.screenName(): String {
return when (filterArgs.type) {
FilterType.STATE -> Analytics.SCREEN_INCIDENT_LIST_BY_LOCATION
FilterType.DATE -> Analytics.SCREEN_INCIDENT_LIST_BY_DATE
FilterType.LATEST -> Analytics.SCREEN_INCIDENT_LIST_MOST_RECENT
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* MIT License
*
* Copyright (c) 2020 Hossain Khan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.blacklivesmatter.policebrutality.ui.incident.arg

import android.os.Parcelable
import androidx.annotation.Keep
import kotlinx.android.parcel.Parcelize

@Parcelize
@Keep
enum class FilterType : Parcelable {
/**
* Shows incidents by selected State
*/
STATE,

/**
* Shows incidents for specific date
*/
DATE,

/**
* Shows most recent incidents first
*/
LATEST
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* MIT License
*
* Copyright (c) 2020 Hossain Khan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.blacklivesmatter.policebrutality.ui.incident.arg

import android.os.Parcelable
import androidx.annotation.Keep
import com.blacklivesmatter.policebrutality.ui.incident.IncidentsFragment
import kotlinx.android.parcel.Parcelize

/**
* Navigation args for [IncidentsFragment] to show different kind of filtered incidents.
* 1. [FilterType.STATE] - where `[stateName]` required and provided.
* 2. [FilterType.DATE] - where both `[timestamp]` and `[dateText]` are required and provided.
* 3. [FilterType.LATEST] - No data required, shows most recent incident first.
*/
@Parcelize
@Keep
data class LocationFilterArgs(
val type: FilterType,
val stateName: String? = null,
val timestamp: Long? = null,
val dateText: String? = null
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import com.blacklivesmatter.policebrutality.analytics.Analytics
import com.blacklivesmatter.policebrutality.config.THE_846_DAY
import com.blacklivesmatter.policebrutality.databinding.FragmentIncidentLocationsBinding
import com.blacklivesmatter.policebrutality.ui.extensions.observeKotlin
import com.blacklivesmatter.policebrutality.ui.incident.arg.FilterType
import com.blacklivesmatter.policebrutality.ui.incident.arg.LocationFilterArgs
import com.blacklivesmatter.policebrutality.ui.incidentlocations.LocationViewModel.NavigationEvent
import com.blacklivesmatter.policebrutality.ui.incidentlocations.LocationViewModel.RefreshEvent
import com.blacklivesmatter.policebrutality.ui.util.IncidentAvailabilityValidator
Expand All @@ -34,6 +36,9 @@ import java.util.ArrayList
import java.util.Calendar
import javax.inject.Inject

/**
* Incidents by US States (location).
*/
@AndroidEntryPoint
class LocationFragment : Fragment() {
@Inject
Expand All @@ -60,7 +65,9 @@ class LocationFragment : Fragment() {
Timber.d("Tapped on state item $state")
analytics.logSelectItem(Analytics.CONTENT_TYPE_LOCATION, state.stateName, state.stateName)
findNavController().navigate(
LocationFragmentDirections.navigationToIncidentsFragment(stateName = state.stateName)
LocationFragmentDirections.navigationToIncidentsFragment(
LocationFilterArgs(type = FilterType.STATE, stateName = state.stateName)
)
)
}
adapter.submitList(emptyList())
Expand Down Expand Up @@ -100,8 +107,11 @@ class LocationFragment : Fragment() {
Timber.d("Navigate incident list for $navigationEvent")
findNavController().navigate(
LocationFragmentDirections.navigationToIncidentsFragment(
timestamp = navigationEvent.timestamp,
dateText = navigationEvent.dateText
LocationFilterArgs(
type = FilterType.DATE,
timestamp = navigationEvent.timestamp,
dateText = navigationEvent.dateText
)
)
)
}
Expand All @@ -115,6 +125,14 @@ class LocationFragment : Fragment() {
}

setupSwipeRefreshAction()

viewDataBinding.showLatestIncidentsFab.setOnClickListener {
findNavController().navigate(
LocationFragmentDirections.navigationToIncidentsFragment(
LocationFilterArgs(type = FilterType.LATEST)
)
)
}
}

override fun onStart() {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,20 @@
tools:itemCount="7"
tools:listitem="@layout/list_item_location" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/show_latest_incidents_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/blue_grey_700"
android:contentDescription="@string/content_description_view_most_recent_incidents_list"
android:text="@string/button_cta_view_most_recent_incidents"
android:textColor="?colorOnSurface"
app:icon="@drawable/ic_protest_strike"
app:iconTint="?colorOnSurface"
app:layout_anchor="@id/appbarlayout"
app:layout_anchorGravity="bottom|right|end" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<import type="android.view.View" />

<variable
name="isDateBased"
name="shouldShowCityAndState"
type="Boolean" />

<variable
Expand Down Expand Up @@ -64,7 +64,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_item_vertical_margin"
android:text="@{safeUnbox(isDateBased)? @string/incident_location_at_city_state_display_text(incident.city, incident.state): @string/incident_location_at_city_display_text(incident.city)}"
android:text="@{safeUnbox(shouldShowCityAndState)? @string/incident_location_at_city_state_display_text(incident.city, incident.state): @string/incident_location_at_city_display_text(incident.city)}"
android:textAlignment="textEnd"
android:textColor="@color/grey_500"
android:visibility="visible"
Expand Down
Loading

0 comments on commit ef97840

Please sign in to comment.