Skip to content

Commit

Permalink
Merge pull request #89 from amardeshbd/develop
Browse files Browse the repository at this point in the history
Release 1.5 prep
  • Loading branch information
hossain-khan authored Jun 12, 2020
2 parents 80199eb + 854cd7b commit c5b985f
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 11 deletions.
7 changes: 7 additions & 0 deletions android-app/.idea/dictionaries/hossain.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions android-app/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ android {
applicationId "com.blacklivesmatter.policebrutality"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode 5
versionName "1.4"
versionCode 6
versionName "1.5"

resConfigs "en"

Expand Down Expand Up @@ -90,7 +90,6 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01"

// Firebase
implementation "com.google.firebase:firebase-analytics:$rootProject.firebaseAnalyticsVersion"
implementation "com.google.firebase:firebase-analytics-ktx:$rootProject.firebaseAnalyticsVersion"
implementation "com.google.firebase:firebase-crashlytics:$rootProject.firebaseCrashlyticsVersion"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.blacklivesmatter.policebrutality.analytics

import android.app.Activity

/**
* Interface to log events for the app.
*/
interface Analytics {
companion object {
const val SCREEN_REPORT_INCIDENT = "ReportNewIncident"
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_DATE_FILTER = "FilterIncidentsByDate"
const val SCREEN_MORE_INFO = "MoreInformation"
const val SCREEN_ABOUT_APP = "AboutApplication"

const val ACTION_INCIDENT_REFRESH = "RefreshIncidents"
const val ACTION_INCIDENT_REPORT_NEW = "MakeIncidentReport"
const val ACTION_SHARE_APP = "ShareApplication"

const val CONTENT_TYPE_LOCATION = "TypeLocation"
const val CONTENT_TYPE_INCIDENT = "TypeIncident"
const val CONTENT_TYPE_INCIDENT_SHARE = "TypeShareIncident"
const val CONTENT_TYPE_HASHTAG = "TypeHashtag"
const val CONTENT_TYPE_PB2020_LINK = "TypePBLink"
}

/**
* Log event when a user views a page.
*/
fun logPageView(activity: Activity, screenName: String)

/**
* Log event when a user searches in the app
*/
fun logSearch(searchTerm: String)

/**
* Log event when a user has selected content in an app
*/
fun logSelectItem(type: String, id: String, name: String)

/**
* Log event when a user has shared content in an app
*/
fun logShare(type: String, id: String)

/**
* Log event when a user action is taken.
*/
fun logEvent(name: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.blacklivesmatter.policebrutality.analytics

import android.app.Activity
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.FirebaseAnalytics.Event
import com.google.firebase.analytics.FirebaseAnalytics.Event.SEARCH
import com.google.firebase.analytics.FirebaseAnalytics.Param.CONTENT_TYPE
import com.google.firebase.analytics.FirebaseAnalytics.Param.ITEM_ID
import com.google.firebase.analytics.FirebaseAnalytics.Param.ITEM_NAME
import com.google.firebase.analytics.FirebaseAnalytics.Param.SEARCH_TERM
import com.google.firebase.analytics.ktx.logEvent
import javax.inject.Inject

/**
* App analytics that uses [FirebaseAnalytics] as backbone.
* See https://firebase.google.com/docs/analytics/get-started?platform=android
*/
class AppAnalytics @Inject constructor(
private val firebaseAnalytics: FirebaseAnalytics
) : Analytics {
override fun logPageView(activity: Activity, screenName: String) {
firebaseAnalytics.setCurrentScreen(activity, screenName, null)
}

override fun logSearch(searchTerm: String) {
firebaseAnalytics.logEvent(SEARCH) {
param(SEARCH_TERM, searchTerm)
}
}

override fun logSelectItem(type: String, id: String, name: String) {
firebaseAnalytics.logEvent(Event.SELECT_CONTENT) {
param(ITEM_ID, id)
param(ITEM_NAME, name)
param(CONTENT_TYPE, type)
}
}

override fun logShare(type: String, id: String) {
firebaseAnalytics.logEvent(Event.SHARE) {
param(ITEM_ID, id)
param(CONTENT_TYPE, type)
}
}

override fun logEvent(name: String) {
firebaseAnalytics.logEvent(name, null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.blacklivesmatter.policebrutality.di.component
import android.app.Application
import com.blacklivesmatter.policebrutality.BrutalityIncidentApplication
import com.blacklivesmatter.policebrutality.di.module.ActivityModule
import com.blacklivesmatter.policebrutality.di.module.AnalyticsModule
import com.blacklivesmatter.policebrutality.di.module.ApiModule
import com.blacklivesmatter.policebrutality.di.module.AppModule
import com.blacklivesmatter.policebrutality.di.module.DaoModule
Expand All @@ -19,12 +20,13 @@ import javax.inject.Singleton
@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class,
ViewModelModule::class,
RepositoryModule::class,
DaoModule::class,
AnalyticsModule::class,
ApiModule::class,
AppModule::class,
DaoModule::class,
RepositoryModule::class,
ViewModelModule::class,
WorkerModule::class
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.blacklivesmatter.policebrutality.di.module

import android.content.Context
import com.blacklivesmatter.policebrutality.analytics.Analytics
import com.blacklivesmatter.policebrutality.analytics.AppAnalytics
import com.google.firebase.analytics.FirebaseAnalytics
import dagger.Module
import dagger.Provides
import timber.log.Timber
import javax.inject.Singleton

@Module
class AnalyticsModule {
@Singleton
@Provides
fun provideAnalytics(context: Context): FirebaseAnalytics {
// https://firebase.google.com/docs/analytics/get-started?platform=android
val instance = FirebaseAnalytics.getInstance(context)
Timber.d("Providing firebase analytics instance: $instance")
return instance
}

@Singleton
@Provides
fun provideAppAnalytics(firebaseAnalytics: FirebaseAnalytics): Analytics {
return AppAnalytics(firebaseAnalytics)
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,55 @@
package com.blacklivesmatter.policebrutality.ui.incident

import android.content.SharedPreferences
import androidx.databinding.ObservableField
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.blacklivesmatter.policebrutality.data.IncidentRepository
import com.blacklivesmatter.policebrutality.data.model.Incident
import com.blacklivesmatter.policebrutality.ui.extensions.LiveEvent
import timber.log.Timber
import javax.inject.Inject

class IncidentViewModel @Inject constructor(
private val incidentRepository: IncidentRepository
private val incidentRepository: IncidentRepository,
private val preferences: SharedPreferences
) : ViewModel() {
companion object {
private const val PREF_KEY_SHARE_CAPABILITY_REMINDER_SHOWN = "preference_key_share_incident_guide_shown"
}

internal val selectedState = ObservableField("")

private val _incidents = MediatorLiveData<List<Incident>>()
val incidents: LiveData<List<Incident>> = _incidents

private val _shareIncident = LiveEvent<Incident>()
val shareIncident: LiveData<Incident> = _shareIncident

private val _shouldShowShareCapabilityMessage = MutableLiveData<Unit>()
val shouldShowShareCapabilityMessage: LiveData<Unit> = _shouldShowShareCapabilityMessage

fun onShareIncidentClicked(incident: Incident) {
Timber.d("User clicked on share incident")
_shareIncident.value = incident
}

fun setArgs(navArgs: IncidentsFragmentArgs) {
navArgs.stateName?.let { selectedSate(it) }
if (navArgs.timestamp != 0L) {
selectedTimestamp(navArgs.timestamp)
}

val isMessageShown = preferences.getBoolean(PREF_KEY_SHARE_CAPABILITY_REMINDER_SHOWN, false)
if (isMessageShown.not()) {
Timber.d("User has launched app for first time, show share capability message.")
preferences.edit().putBoolean(PREF_KEY_SHARE_CAPABILITY_REMINDER_SHOWN, true).apply()
_shouldShowShareCapabilityMessage.value = Unit
} else {
Timber.d("Not first time app launch, don't show share capability message.")
}
}

private fun selectedSate(stateName: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,34 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.blacklivesmatter.policebrutality.R
import com.blacklivesmatter.policebrutality.analytics.Analytics
import com.blacklivesmatter.policebrutality.analytics.Analytics.Companion.CONTENT_TYPE_INCIDENT_SHARE
import com.blacklivesmatter.policebrutality.data.model.Incident
import com.blacklivesmatter.policebrutality.databinding.DialogBottomsheetIncidentDetailsBinding
import com.blacklivesmatter.policebrutality.databinding.FragmentIncidentsBinding
import com.blacklivesmatter.policebrutality.ui.extensions.observeKotlin
import com.blacklivesmatter.policebrutality.ui.util.IntentBuilder
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.snackbar.Snackbar
import dagger.android.support.DaggerFragment
import timber.log.Timber
import javax.inject.Inject

/**
* Shows list of incidents that happened during the peaceful protest.
*/
class IncidentsFragment : DaggerFragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

@Inject
lateinit var analytics: Analytics

private val viewModel by viewModels<IncidentViewModel> { viewModelFactory }
private lateinit var viewDataBinding: FragmentIncidentsBinding
private val navArgs: IncidentsFragmentArgs by navArgs()
private lateinit var adapter: IncidentsAdapter
private lateinit var bottomSheetShareDialog: BottomSheetDialog

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
viewDataBinding = FragmentIncidentsBinding.inflate(inflater, container, false).apply {
Expand All @@ -39,6 +52,12 @@ class IncidentsFragment : DaggerFragment() {
isDateBasedIncidents = navArgs.isDateBased(),
itemClickCallback = { clickedIncident ->
Timber.d("Selected Incident: $clickedIncident")
analytics.logSelectItem(
type = Analytics.CONTENT_TYPE_INCIDENT,
id = clickedIncident.id,
name = clickedIncident.incident_id ?: "---"
)
showIncidentDetailsForSharing(clickedIncident)
}, linkClickCallback = { clickedLink ->
openWebPage(clickedLink)
}
Expand All @@ -62,6 +81,48 @@ class IncidentsFragment : DaggerFragment() {
viewModel.incidents.observe(viewLifecycleOwner, Observer {
adapter.submitList(it)
})

viewModel.shareIncident.observeKotlin(viewLifecycleOwner) { incident ->
if (bottomSheetShareDialog.isShowing) {
bottomSheetShareDialog.dismiss()
}
analytics.logSelectItem(CONTENT_TYPE_INCIDENT_SHARE, incident.id, incident.incident_id ?: "---")
startActivity(IntentBuilder.share(incident))
}

viewModel.shouldShowShareCapabilityMessage.observeKotlin(viewLifecycleOwner) {
Snackbar.make(
viewDataBinding.root,
R.string.message_share_incident_capability,
Snackbar.LENGTH_INDEFINITE
).setAction(R.string.button_cta_thanks, {}).show()
}
}

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
)
}
}

private fun showIncidentDetailsForSharing(incident: Incident) {
Timber.d("User tapped on the incident item. Show details and allow sharing.")
val context = context ?: return

val incidentBinding = DialogBottomsheetIncidentDetailsBinding.inflate(layoutInflater, null, false).apply {
lifecycleOwner = this@IncidentsFragment
data = incident // Allows in future to use data binding to provide more info in bottom sheet dialog
vm = viewModel
}

bottomSheetShareDialog = BottomSheetDialog(context)
bottomSheetShareDialog.setContentView(incidentBinding.root)
bottomSheetShareDialog.dismissWithAnimation = true
bottomSheetShareDialog.show()
}

/**
Expand All @@ -77,7 +138,7 @@ class IncidentsFragment : DaggerFragment() {
}
}

private fun IncidentsFragmentArgs.isDateBased(): Boolean = navArgs.timestamp != 0L
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.blacklivesmatter.policebrutality.R
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
Expand All @@ -37,6 +38,9 @@ class LocationFragment : DaggerFragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

@Inject
lateinit var analytics: Analytics

private val viewModel by viewModels<LocationViewModel> { viewModelFactory }
private lateinit var viewDataBinding: FragmentIncidentLocationsBinding
private lateinit var adapter: LocationListAdapter
Expand All @@ -53,6 +57,7 @@ class LocationFragment : DaggerFragment() {

adapter = LocationListAdapter { state ->
Timber.d("Tapped on state item $state")
analytics.logSelectItem(Analytics.CONTENT_TYPE_LOCATION, state.stateName, state.stateName)
findNavController().navigate(
LocationFragmentDirections.navigationToIncidentsFragment(stateName = state.stateName)
)
Expand Down Expand Up @@ -107,6 +112,11 @@ class LocationFragment : DaggerFragment() {
setupSwipeRefreshAction()
}

override fun onStart() {
super.onStart()
activity?.let { analytics.logPageView(it, Analytics.SCREEN_INCIDENT_LOCATION) }
}

private fun setupSwipeRefreshAction() {
viewDataBinding.swipeRefresh.setColorSchemeResources(R.color.teal_700, R.color.blue_grey_500, R.color.teal_800)
viewDataBinding.swipeRefresh.setOnRefreshListener {
Expand Down Expand Up @@ -188,5 +198,6 @@ class LocationFragment : DaggerFragment() {
viewModel.onDateTimeStampSelected(viewLifecycleOwner, selectedTimeStamp)
}
picker.show(childFragmentManager, picker.toString())
activity?.let { analytics.logPageView(it, Analytics.SCREEN_INCIDENT_DATE_FILTER) }
}
}
Loading

0 comments on commit c5b985f

Please sign in to comment.