Skip to content

Commit

Permalink
feat(android): list user container bookmarks (#229)
Browse files Browse the repository at this point in the history
Refs: closes #225, closes #226

## Summary

List the user's container bookmarks in the user account activity.
For each bookmark, display the container categories and allow the user
to remove the bookmark.

---------

Co-authored-by: joaotomaspinheiro <[email protected]>
  • Loading branch information
Goncalo-Marques and joaotomaspinheiro authored Jun 8, 2024
1 parent 3724d0d commit 5d65edd
Show file tree
Hide file tree
Showing 22 changed files with 543 additions and 78 deletions.
66 changes: 57 additions & 9 deletions android/app/src/main/java/com/ecomap/ecomap/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
*/
private lateinit var containerClusterManager: ClusterManager<ContainerMarker>

/**
* Map containing the container markers, merging those that are in the same position to be
* contained in the same marker.
*/
private val containerMarkers = mutableMapOf<LatLng, ContainerMarker>()

/**
* Defines the current container category filter.
*/
private var currentContainerCategoryFilter: ContainerCategory? = null

/**
* Defines the group of buttons view.
*/
Expand Down Expand Up @@ -196,6 +207,34 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
closeContainerInfoWindow()
}

override fun onStart() {
super.onStart()

// Focus on the start location, if available.
if (startFocusLocation != null) {
val container = containerMarkers[startFocusLocation]
if (container != null) {
showContainerInfoWindow(container)
map.animateCamera(
CameraUpdateFactory.newLatLngZoom(
container.position,
MAP_CAMERA_ZOOM_CONTAINER_FOCUS
)
)
}
}

// Get user container bookmarks.
getUserContainerBookmarks()
}

override fun onStop() {
super.onStop()

// Reset the start focus location.
startFocusLocation = null
}

/**
* Populates the given chip group with all the available container categories.
*/
Expand Down Expand Up @@ -230,6 +269,10 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
*/
private fun openUserAccountScreen() {
val intentUserAccountActivity = Intent(this, UserAccountActivity::class.java)
intentUserAccountActivity.putExtra(
UserAccountActivity.INTENT_EXTRA_CONTAINER_CATEGORY,
currentContainerCategoryFilter?.ordinal
)
startActivity(intentUserAccountActivity)
}

Expand Down Expand Up @@ -279,9 +322,6 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
// Adds the containers in the map.
updateContainersUI()

// Get user container bookmarks.
getUserContainerBookmarks()

// Get the current location of the device and set the position of the map.
focusMyLocation()
}
Expand Down Expand Up @@ -368,12 +408,11 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
* Updates the map UI by adding the containers as markers using the provided filter.
*/
private fun updateContainersUI(containerCategoryFilter: ContainerCategory? = null) {
currentContainerCategoryFilter = containerCategoryFilter

// Clear the current markers.
containerClusterManager.clearItems()

// Map containing the filtered containers, merging those that are in the same position to be
// contained in the same marker.
val filteredContainers = mutableMapOf<LatLng, ContainerMarker>()
containerMarkers.clear()

// Helper function to handle a successful response.
val handleSuccess = fun(paginatedContainers: ContainersPaginated) {
Expand All @@ -387,15 +426,15 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {

// Add the marker if it is not currently in the Cluster Manager, otherwise append
// the container category to the existing marker.
val existingContainer = filteredContainers[containerPosition]
val existingContainer = containerMarkers[containerPosition]
if (existingContainer == null) {
val containerMarker = ContainerMarker(
this,
arrayListOf(container)
)

containerClusterManager.addItem(containerMarker)
filteredContainers[containerPosition] = containerMarker
containerMarkers[containerPosition] = containerMarker
} else {
existingContainer.containers.add(container)
}
Expand Down Expand Up @@ -584,6 +623,7 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
// Execute the request to get all existing user container bookmarks and add them to the list.
val request = ApiClient.listUserContainerBookmarks(
userID,
null,
REQUEST_LIST_CONTAINER_LIMIT,
0,
token,
Expand All @@ -593,6 +633,7 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
ApiRequestQueue.getInstance(applicationContext).add(
ApiClient.listUserContainerBookmarks(
userID,
null,
REQUEST_LIST_CONTAINER_LIMIT,
REQUEST_LIST_CONTAINER_LIMIT * i,
token,
Expand All @@ -610,6 +651,12 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
}

companion object {
/**
* Defines the location to focus on start.
* It is reset to null on the stop activity event.
*/
var startFocusLocation: LatLng? = null

private val LOG_TAG = MainActivity::class.java.simpleName

private const val PERMISSIONS_REQUEST_ACCESS_LOCATION = 1
Expand All @@ -625,6 +672,7 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private const val MAP_PADDING_BOTTOM = 32

private const val MAP_CAMERA_ZOOM_DEFAULT = 15.0F
private const val MAP_CAMERA_ZOOM_CONTAINER_FOCUS = 17.0F

private const val REQUEST_LIST_CONTAINER_LIMIT = 100

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ object ApiClient {
private const val FIELD_CONTAINER_CREATED_AT = "createdAt"
private const val FIELD_CONTAINER_MODIFIED_AT = "modifiedAt"
private const val FIELD_CONTAINERS = "containers"
private const val FIELD_FILTER_CONTAINER_CATEGORY = "containerCategory"

/**
* Signs in a user with a given username and password.
Expand Down Expand Up @@ -215,8 +216,10 @@ object ApiClient {
}

/**
* Returns the user container bookmarks with the specified filter.
* Returns the user container bookmarks with the specified filter. The bookmarks are sorted by
* descending order of the date they were created.
* @param userID User identifier.
* @param containerCategory Container category to filter by.
* @param limit Amount of resources to get for the provided filter.
* @param offset Amount of resources to skip for the provided filter.
* @param token JWT authorization token.
Expand All @@ -226,15 +229,20 @@ object ApiClient {
*/
fun listUserContainerBookmarks(
userID: String,
containerCategory: ContainerCategory? = null,
limit: Int,
offset: Int,
token: String,
listener: Listener<ContainersPaginated>,
errorListener: ErrorListener
): JsonObjectRequest {
val url = "$URL_USERS/$userID$URL_BOOKMARK_CONTAINERS" +
var url = "$URL_USERS/$userID$URL_BOOKMARK_CONTAINERS" +
"?$FIELD_NAME_PAGINATION_LIMIT=$limit" +
"&$FIELD_NAME_PAGINATION_OFFSET=$offset"
"&$FIELD_NAME_PAGINATION_OFFSET=$offset" +
"&sort=createdAt&order=desc"
if (containerCategory != null) {
url += "&$FIELD_FILTER_CONTAINER_CATEGORY=${mapDomainContainerCategory(containerCategory)}"
}

return object : JsonObjectRequest(
Method.GET, url, null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import com.ecomap.ecomap.R
*/
data class ContainerCategoryRecyclerViewData(
val iconResourceID: Int,
val category: String,
val category: String = "",
)

/**
Expand All @@ -35,7 +35,7 @@ class ContainerCategoriesRecyclerViewAdapter(private val dataSet: Array<Containe
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item.
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.frame_layout_container_categories, viewGroup, false)
.inflate(R.layout.container_category, viewGroup, false)

return ViewHolder(view)
}
Expand All @@ -45,6 +45,11 @@ class ContainerCategoriesRecyclerViewAdapter(private val dataSet: Array<Containe

viewHolder.imageView.setImageResource(data.iconResourceID)
viewHolder.textView.text = data.category

if (data.category.isBlank()) {
// Make the category description invisible if it is not provided.
viewHolder.textView.visibility = View.GONE
}
}

override fun getItemCount(): Int {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.ecomap.ecomap.user

import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ecomap.ecomap.MainActivity
import com.ecomap.ecomap.R
import com.ecomap.ecomap.domain.Container
import com.ecomap.ecomap.map.ContainerCategoriesRecyclerViewAdapter
import com.ecomap.ecomap.map.ContainerCategoryRecyclerViewData
import com.google.android.gms.maps.model.LatLng

/**
* Represents the structure of an item in the container bookmark recycler view.
* @param containers Containers associated with the bookmark.
*/
data class ContainerBookmarkRecyclerViewData(val containers: ArrayList<Container>)

/**
* Recycler view adapter for the container bookmarks.
*/
class ContainerBookmarksRecyclerViewAdapter(
private val context: Context,
private val activity: Activity,
private val dataSet: ArrayList<ContainerBookmarkRecyclerViewData>
) :
RecyclerView.Adapter<ContainerBookmarksRecyclerViewAdapter.ViewHolder>() {
/**
* Invoked when a button container bookmark is clicked for a specific position in the data set.
*/
var onButtonContainerBookmarkClicked: ((position: Int) -> Unit)? = null

/**
* Defines the views in the adapter.
*/
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val constraintLayout: ConstraintLayout = view.findViewById(R.id.constraint_layout)
val textViewMunicipalityName: TextView = view.findViewById(R.id.text_view_municipality_name)
val textViewWayName: TextView = view.findViewById(R.id.text_view_way_name)
val buttonContainerBookmark: ImageButton =
view.findViewById(R.id.button_container_bookmark)
val recyclerContainerCategories: RecyclerView =
view.findViewById(R.id.recycler_container_categories)
}

override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item.
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.container_bookmark, viewGroup, false)

return ViewHolder(view)
}

override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val data = dataSet[position]

// Set location text data.
if (data.containers.isNotEmpty()) {
val container = data.containers[0]

viewHolder.constraintLayout.setOnClickListener { focusContainer(container) }

viewHolder.textViewMunicipalityName.text = container.geoJSON.properties.municipalityName
viewHolder.textViewWayName.text = container.geoJSON.properties.getWayName(context)
}

// Set button functions.
viewHolder.buttonContainerBookmark.setOnClickListener {
onButtonContainerBookmarkClicked?.invoke(position)
}

// Populate the container category recycler view.
val containerCategoriesDataSet =
ArrayList<ContainerCategoryRecyclerViewData>(data.containers.size)
for (container in data.containers) {
val categoryData =
ContainerCategoryRecyclerViewData(container.category.getIconResource())
if (containerCategoriesDataSet.contains(categoryData)) {
// The category already exists in the current data set.
continue
}

containerCategoriesDataSet.add(categoryData)
}

viewHolder.recyclerContainerCategories.layoutManager =
LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
viewHolder.recyclerContainerCategories.adapter =
ContainerCategoriesRecyclerViewAdapter(containerCategoriesDataSet.toTypedArray())
}

override fun getItemCount(): Int {
return dataSet.size
}

/**
* Terminates the current activity focus on the container location.
* @param container Container to focus.
*/
private fun focusContainer(container: Container) {
val containerCoordinates = container.geoJSON.geometry.coordinates
val containerPosition = LatLng(containerCoordinates[1], containerCoordinates[0])

MainActivity.startFocusLocation = containerPosition
activity.finish()
}
}
Loading

0 comments on commit 5d65edd

Please sign in to comment.