diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4226669 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug, help wanted, question +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Smartphone (please complete the following information):** + - Device: [e.g. Galaxy S9+] + - OS Version: [e.g. 9.0] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..dc82ecd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement, help wanted +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index 4602b6e..9b2d9df 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ Android client app for https://github.com/2020PB/police-brutality (Repository co
-### Early BETA Testing +### Early BETA Testing 🚧 If you want to try the app as it is being developed, you can get the latest Android APK in 2 different ways: * GitHub Releases - See [current release](https://github.com/amardeshbd/android-police-brutality-incidents/releases) with APK bundled with it. * Firebase App Distribution - Subscribe to new updates via email. Use this [open-beta testing](https://appdistribution.firebase.dev/i/5d2cb8359305f7e7) process. The invite email will be sent on next release, not immediately. +> NOTE: App will be available in Google Play in few days after it's reviewed and approved. + ## Objective The objective of the app is to be front-end of the data that is collected and exposed by [police-brutality](https://github.com/2020PB/police-brutality) repository. diff --git a/android-app/.idea/codeStyles/Project.xml b/android-app/.idea/codeStyles/Project.xml index b93b279..1dd7d82 100644 --- a/android-app/.idea/codeStyles/Project.xml +++ b/android-app/.idea/codeStyles/Project.xml @@ -1,6 +1,11 @@+ *
+ * In the implementation of this call-back method, you should consider invoking + * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any + * non-trivial field of the {@code src} object. However, you should never invoke it on the + * {@code src} object itself since that will cause an infinite loop (Gson will call your + * call-back method again). + * + * @param src the object that needs to be converted to Json. + * @param typeOfSrc the actual type (fully genericized version) of the source object. + * @return a JsonElement corresponding to the specified object. + */ + @Override + public JsonElement serialize(final OffsetDateTime src, final Type typeOfSrc, final JsonSerializationContext context) { + return new JsonPrimitive(FORMATTER.format(src)); + } + + /** + * Gson invokes this call-back method during deserialization when it encounters a field of the + * specified type.
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
+ * for any non-trivial field of the returned object. However, you should never invoke it on the
+ * the same type passing {@code json} since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param json The Json data being deserialized
+ * @param typeOfT The type of the Object to deserialize to
+ * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
+ * @throws JsonParseException if json is not in the expected format of {@code typeOfT}
+ */
+ @Override
+ public OffsetDateTime deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
+ throws JsonParseException {
+ return FORMATTER.parse(json.getAsString(), OffsetDateTime.FROM);
+ }
+}
diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/GeoCoding.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/GeoCoding.kt
new file mode 100644
index 0000000..ddb6d9b
--- /dev/null
+++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/GeoCoding.kt
@@ -0,0 +1,24 @@
+package com.blacklivesmatter.policebrutality.data.model
+
+import androidx.annotation.Keep
+import androidx.room.ColumnInfo
+import com.google.gson.annotations.SerializedName
+
+/**
+ * Inner object of [Incident].
+ *
+ * Sample data:
+ * ```
+ * {
+ * "lat":"36.3728538",
+ * "long":"-94.2088172"
+ * }
+ * ```
+ *
+ * See [Incident]
+ */
+@Keep
+data class GeoCoding(
+ @SerializedName("lat") @ColumnInfo(name = "lat") val lat: String? = null,
+ @SerializedName("long") @ColumnInfo(name = "long") val long: String? = null
+)
diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/Incident.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/Incident.kt
index e796bd8..7314ae5 100644
--- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/Incident.kt
+++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/data/model/Incident.kt
@@ -1,42 +1,70 @@
package com.blacklivesmatter.policebrutality.data.model
+import androidx.annotation.Keep
import androidx.room.ColumnInfo
import androidx.room.Entity
+import androidx.room.Ignore
import androidx.room.PrimaryKey
+import com.blacklivesmatter.policebrutality.config.THE_846_DAY
import com.google.gson.annotations.SerializedName
+import org.threeten.bp.OffsetDateTime
+import org.threeten.bp.format.DateTimeFormatter
+import org.threeten.bp.format.FormatStyle
/**
* An example data exposed from JSON response.
*
* Sample data:
- *
* ```
* {
- * "links":[
- * "https://twitter.com/courtenay_roche/status/1267653137969623040",
- * "https://twitter.com/yagirlbrookie09/status/1267647898365427714",
- * "https://www.4029tv.com/article/bentonville-police-deploy-tear-gas-on-protesters/32736629#"
- * ],
+ * "id":"7b044f80-a9d6-11ea-823c-cb6b88d56860",
+ * "pb_id":"ar-bentonville-1",
* "state":"Arkansas",
- * "edit_at":"https://github.com/2020PB/police-brutality/blob/master/reports/Arkansas.md",
* "city":"Bentonville",
- * "name":"Law enforcement gas a crowd chanting \u201cwe want peace\u201d right after exiting the building.",
- * "date":"2020-06-01",
- * "date_text":"June 1st",
- * "id": "ar-bentonville-1"
+ * "date":"2020-06-01T00:00:00.000000Z",
+ * "title":"Law enforcement gas a crowd chanting \u201cwe want peace\u201d right after exiting the building.",
+ * "description":null,
+ * "links":[
+ * "https:\/\/twitter.com\/courtenay_roche\/status\/1267653137969623040",
+ * "https:\/\/twitter.com\/yagirlbrookie09\/status\/1267647898365427714",
+ * "https:\/\/www.4029tv.com\/article\/bentonville-police-deploy-tear-gas-on-protesters\/32736629#"
+ * ],
+ * "data":null,
+ * "geocoding":{
+ * "lat":"36.3728538",
+ * "long":"-94.2088172"
+ * }
* }
* ```
+ *
+ * ### See
+ * - [Sample API response](https://api.846policebrutality.com/api/incidents)
+ * - [Support date+time in Room DB](https://medium.com/androiddevelopers/room-time-2b4cf9672b98)
*/
+@Keep
@Entity(tableName = "incidents")
data class Incident(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = "_id") val _id: Long,
- @SerializedName("id") @ColumnInfo(name = "incident_id") val incident_id: String? = "",
- @ColumnInfo(name = "state") val state: String? = "",
- @ColumnInfo(name = "edit_url") val edit_at: String? = "",
- @ColumnInfo(name = "city") val city: String? = "",
- @ColumnInfo(name = "name") val name: String? = "",
- @ColumnInfo(name = "date") val date: String? = "", // TODO - should use proper date to be able to use it as filter
- @ColumnInfo(name = "date_text") val date_text: String? = "",
- @ColumnInfo(name = "links") val links: List>()
val incidents: LiveData
> = _incidents
- fun selectedSate(stateName: String) {
+ fun setArgs(navArgs: IncidentsFragmentArgs) {
+ navArgs.stateName?.let { selectedSate(it) }
+ if (navArgs.timestamp != 0L) {
+ selectedTimestamp(navArgs.timestamp)
+ }
+ }
+
+ private fun selectedSate(stateName: String) {
selectedState.set(stateName)
_incidents.addSource(incidentRepository.getStateIncidents(stateName)) {
@@ -25,4 +32,11 @@ class IncidentViewModel @Inject constructor(
_incidents.value = it
}
}
+
+ private fun selectedTimestamp(timestamp: Long) {
+ _incidents.addSource(incidentRepository.getIncidentsByDate(timestamp)) {
+ Timber.d("Incidents Updated ")
+ _incidents.value = it
+ }
+ }
}
diff --git a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsAdapter.kt b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsAdapter.kt
index 51bf894..fc65ebd 100644
--- a/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsAdapter.kt
+++ b/android-app/app/src/main/java/com/blacklivesmatter/policebrutality/ui/incident/IncidentsAdapter.kt
@@ -11,6 +11,7 @@ import com.blacklivesmatter.policebrutality.databinding.ListItemIncidentCoreBind
import com.blacklivesmatter.policebrutality.ui.common.DataBoundListAdapter
class IncidentsAdapter constructor(
+ private val isDateBasedIncidents: Boolean,
private val itemClickCallback: ((Incident) -> Unit)?,
private val linkClickCallback: ((String) -> Unit)? = null
) : DataBoundListAdapter
> = incidentRepository.getLocations()
+
+ private val _dateFilterMediatorEvent = MediatorLiveData