diff --git a/android-app/.idea/dictionaries/hossain.xml b/android-app/.idea/dictionaries/hossain.xml new file mode 100644 index 0000000..61ca0ea --- /dev/null +++ b/android-app/.idea/dictionaries/hossain.xml @@ -0,0 +1,7 @@ + + + + hashtag + + + \ No newline at end of file diff --git a/android-app/app/build.gradle b/android-app/app/build.gradle index 9684868..6779f8e 100644 --- a/android-app/app/build.gradle +++ b/android-app/app/build.gradle @@ -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" @@ -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" diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/analytics/Analytics.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/analytics/Analytics.kt new file mode 100644 index 0000000..d4f3a14 --- /dev/null +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/analytics/Analytics.kt @@ -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) +} diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/analytics/AppAnalytics.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/analytics/AppAnalytics.kt new file mode 100644 index 0000000..8342adb --- /dev/null +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/analytics/AppAnalytics.kt @@ -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) + } +} diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/component/AppComponent.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/component/AppComponent.kt index a6476c0..ae30165 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/component/AppComponent.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/component/AppComponent.kt @@ -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 @@ -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 ] ) diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/module/AnalyticsModule.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/module/AnalyticsModule.kt new file mode 100644 index 0000000..3afabdf --- /dev/null +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/di/module/AnalyticsModule.kt @@ -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) + } +} diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentViewModel.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentViewModel.kt index 217ac37..8a97508 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentViewModel.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentViewModel.kt @@ -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>() val incidents: LiveData> = _incidents + private val _shareIncident = LiveEvent() + val shareIncident: LiveData = _shareIncident + + private val _shouldShowShareCapabilityMessage = MutableLiveData() + val shouldShowShareCapabilityMessage: LiveData = _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) { diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsFragment.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsFragment.kt index c29fe7e..9e13f2e 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsFragment.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsFragment.kt @@ -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 { 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 { @@ -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) } @@ -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() } /** @@ -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 diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationFragment.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationFragment.kt index 20da5c5..f7daef7 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationFragment.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationFragment.kt @@ -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 @@ -37,6 +38,9 @@ class LocationFragment : DaggerFragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var analytics: Analytics + private val viewModel by viewModels { viewModelFactory } private lateinit var viewDataBinding: FragmentIncidentLocationsBinding private lateinit var adapter: LocationListAdapter @@ -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) ) @@ -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 { @@ -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) } } } diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationViewModel.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationViewModel.kt index 9d07132..188127d 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationViewModel.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incidentlocations/LocationViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.blacklivesmatter.policebrutality.analytics.Analytics import com.blacklivesmatter.policebrutality.data.IncidentRepository import com.blacklivesmatter.policebrutality.data.model.Incident import com.blacklivesmatter.policebrutality.data.model.LocationIncidents @@ -22,7 +23,8 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class LocationViewModel @Inject constructor( - private val incidentRepository: IncidentRepository + private val incidentRepository: IncidentRepository, + private val analytics: Analytics ) : ViewModel() { sealed class NavigationEvent { data class Filter(val timestamp: Long, val dateText: String) : NavigationEvent() @@ -75,6 +77,7 @@ class LocationViewModel @Inject constructor( Timber.w("Already loading content. Ignore additional refresh request.") return } + analytics.logEvent(Analytics.ACTION_INCIDENT_REFRESH) isOperationInProgress.set(true) _refreshEvent.value = RefreshEvent.Loading diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/moreinfo/MoreInfoFragment.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/moreinfo/MoreInfoFragment.kt index dc3bcc1..f9110f6 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/moreinfo/MoreInfoFragment.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/moreinfo/MoreInfoFragment.kt @@ -15,6 +15,7 @@ import androidx.core.view.children import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import com.blacklivesmatter.policebrutality.R +import com.blacklivesmatter.policebrutality.analytics.Analytics import com.blacklivesmatter.policebrutality.databinding.FragmentMoreInfoBinding import com.blacklivesmatter.policebrutality.ui.extensions.observeKotlin import com.blacklivesmatter.policebrutality.ui.util.IntentBuilder @@ -30,6 +31,9 @@ class MoreInfoFragment : DaggerFragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var analytics: Analytics + private val viewModel by viewModels { viewModelFactory } private lateinit var viewDataBinding: FragmentMoreInfoBinding @@ -57,8 +61,14 @@ class MoreInfoFragment : DaggerFragment() { handleExternalUrl() } + override fun onStart() { + super.onStart() + activity?.let { analytics.logPageView(it, Analytics.SCREEN_MORE_INFO) } + } + private fun handleExternalUrl() { viewModel.openExternalUrl.observeKotlin(viewLifecycleOwner) { url -> + analytics.logSelectItem(Analytics.CONTENT_TYPE_PB2020_LINK, url, url) val intent = IntentBuilder.build(requireContext(), url) if (intent != null) { startActivity(intent) @@ -86,6 +96,7 @@ class MoreInfoFragment : DaggerFragment() { } private fun copyTextToClipboard(copyText: String) { + analytics.logSelectItem(Analytics.CONTENT_TYPE_HASHTAG, copyText, copyText) val clipboardManager: ClipboardManager = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java)!! val clipData = ClipData.newPlainText("text", copyText) @@ -117,12 +128,14 @@ class MoreInfoFragment : DaggerFragment() { Timber.d("Share app menu item selected.") // TODO - update this whenever app is published at // https://play.google.com/store/apps/details?id=com.blacklivesmatter.policebrutality + // TODO https://github.com/amardeshbd/android-police-brutality-incidents/issues/55 Snackbar.make( viewDataBinding.root, "Sharing coming soon: This app is pending approval on Google Play Store. " + "Thanks for caring! ❤️", Snackbar.LENGTH_LONG ).show() + analytics.logEvent(Analytics.ACTION_SHARE_APP) return true } else -> { @@ -136,5 +149,6 @@ class MoreInfoFragment : DaggerFragment() { .setPositiveButton(R.string.button_cta_okay, null) .setView(R.layout.dialog_about_app) .show() + activity?.let { analytics.logPageView(it, Analytics.SCREEN_ABOUT_APP) } } } diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportFragment.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportFragment.kt index d0dbcb0..4955c05 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportFragment.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportFragment.kt @@ -9,6 +9,7 @@ import androidx.core.text.HtmlCompat import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import com.blacklivesmatter.policebrutality.R +import com.blacklivesmatter.policebrutality.analytics.Analytics import com.blacklivesmatter.policebrutality.databinding.FragmentNewReportBinding import com.blacklivesmatter.policebrutality.ui.extensions.observeKotlin import com.blacklivesmatter.policebrutality.ui.util.IntentBuilder @@ -21,6 +22,9 @@ class NewReportFragment : DaggerFragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var analytics: Analytics + private val viewModel by viewModels { viewModelFactory } private lateinit var viewDataBinding: FragmentNewReportBinding @@ -51,6 +55,11 @@ class NewReportFragment : DaggerFragment() { } } + override fun onStart() { + super.onStart() + activity?.let { analytics.logPageView(it, Analytics.SCREEN_REPORT_INCIDENT) } + } + private fun bindGuideText(textView: MaterialTextView) { val message = HtmlCompat.fromHtml( resources.getText(R.string.report_new_incident_guideline_text) as String, diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportViewModel.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportViewModel.kt index 57a2999..5e5ec1f 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportViewModel.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/newreport/NewReportViewModel.kt @@ -2,15 +2,19 @@ package com.blacklivesmatter.policebrutality.ui.newreport import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import com.blacklivesmatter.policebrutality.analytics.Analytics import com.blacklivesmatter.policebrutality.config.REPORT_INCIDENT_WEB_URL import com.blacklivesmatter.policebrutality.ui.extensions.LiveEvent import javax.inject.Inject -class NewReportViewModel @Inject constructor() : ViewModel() { +class NewReportViewModel @Inject constructor( + private val analytics: Analytics +) : ViewModel() { private val _openReportIncidentUrl = LiveEvent() val openReportIncidentUrl: LiveData = _openReportIncidentUrl fun onReportIncidentClicked() { + analytics.logEvent(Analytics.ACTION_INCIDENT_REPORT_NEW) _openReportIncidentUrl.value = REPORT_INCIDENT_WEB_URL } } diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/util/IntentBuilder.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/util/IntentBuilder.kt index ca2c742..e66f9fd 100644 --- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/util/IntentBuilder.kt +++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/util/IntentBuilder.kt @@ -3,6 +3,8 @@ package com.blacklivesmatter.policebrutality.ui.util import android.content.Context import android.content.Intent import android.net.Uri +import com.blacklivesmatter.policebrutality.config.PB_LINK_WEB +import com.blacklivesmatter.policebrutality.data.model.Incident object IntentBuilder { // Google Play Application IDs that allows to launch appropriate app without having chooser intent. @@ -57,4 +59,35 @@ object IntentBuilder { // Finally if nothing works, return null as error indication return null } + + /** + * Builds intent to share [incident] via email or other social network + * + * See: https://developer.android.com/training/sharing/send#send-text-content + */ + fun share(incident: Incident): Intent { + // First, build the share texts + val shareContentTitle = "Check this incident that was reported at $PB_LINK_WEB" + + val shareBodyText = """ +$shareContentTitle + +- Incident: ${incident.name} +- Location: ${incident.city}, ${incident.state} +- Date: ${incident.date} + +Reference/Evidence Links: +${incident.links.joinToString(separator = " \n * ", prefix = " * ")} + """.trimIndent() + + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, shareBodyText) + putExtra(Intent.EXTRA_SUBJECT, shareContentTitle) + type = "text/plain" + } + + val shareIntent = Intent.createChooser(sendIntent, null) + return shareIntent + } } diff --git a/android-app/app/src/main/res/layout/dialog_bottomsheet_incident_details.xml b/android-app/app/src/main/res/layout/dialog_bottomsheet_incident_details.xml new file mode 100644 index 0000000..1d91543 --- /dev/null +++ b/android-app/app/src/main/res/layout/dialog_bottomsheet_incident_details.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/values/strings.xml b/android-app/app/src/main/res/values/strings.xml index 2603a05..863ae1f 100644 --- a/android-app/app/src/main/res/values/strings.xml +++ b/android-app/app/src/main/res/values/strings.xml @@ -26,6 +26,8 @@ Sorry, there are no incidents reported on selected date %1$s Successfully refreshed %1$s incidents from source \'2020PB/police-brutality\'. Sorry, we are unable to refresh new incidents data at the moment. Please try again later. + Share this incident with %1$s external link(s)? + Did you know, you can share incident details by just tapping on it? Justice for George Floyd protest fist image icon @@ -38,6 +40,8 @@ About App Credits OK + Thanks + Share Incident Total %d reported incidents.