Skip to content

Commit

Permalink
Update offline areas list UI
Browse files Browse the repository at this point in the history
  • Loading branch information
gino-m committed Oct 6, 2023
1 parent 14ef4b9 commit f6f28af
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,34 @@
*/
package com.google.android.ground.ui.offlineareas

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.ground.databinding.OfflineAreasListItemBinding
import com.google.android.ground.model.imagery.OfflineArea
import com.google.android.ground.ui.common.Navigator

internal class OfflineAreaListAdapter(private val navigator: Navigator) :
internal class OfflineAreaListAdapter() :
RecyclerView.Adapter<OfflineAreaListAdapter.ViewHolder>() {

private var offlineAreas: List<OfflineArea> = listOf()
private var offlineAreas: List<OfflineAreaListItemViewModel> = listOf()

class ViewHolder
internal constructor(
var binding: OfflineAreasListItemBinding,
var areas: List<OfflineArea>,
private val navigator: Navigator
) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
init {
binding.offlineAreaName.setOnClickListener(this)
}

override fun onClick(v: View) {
if (areas.isNotEmpty()) {
val id = areas[adapterPosition].id
navigator.navigate(OfflineAreasFragmentDirections.viewOfflineArea(id))
}
}
}
class ViewHolder internal constructor(var binding: OfflineAreasListItemBinding) :
RecyclerView.ViewHolder(binding.root)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val offlineAreasListItemBinding =
OfflineAreasListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(offlineAreasListItemBinding, offlineAreas, navigator)
return ViewHolder(offlineAreasListItemBinding)
}

override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.areas = offlineAreas
viewHolder.binding.offlineAreaName.text = offlineAreas[position].name
viewHolder.binding.viewModel = offlineAreas[position]
}

override fun getItemCount(): Int = offlineAreas.size

fun update(offlineAreas: List<OfflineArea>) {
@SuppressLint("NotifyDataSetChanged")
fun update(offlineAreas: List<OfflineAreaListItemViewModel>) {
this.offlineAreas = offlineAreas
notifyDataSetChanged()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.ground.ui.offlineareas

import com.google.android.ground.model.imagery.OfflineArea
import com.google.android.ground.ui.common.Navigator

class OfflineAreaListItemViewModel(
private val navigator: Navigator,
val area: OfflineArea,
val sizeOnDisk: String
) {
val areaName = area.name

fun onClick() {
navigator.navigate(OfflineAreasFragmentDirections.viewOfflineArea(area.id))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class OfflineAreasFragment : Hilt_OfflineAreasFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = getViewModel(OfflineAreasViewModel::class.java)
offlineAreaListAdapter = OfflineAreaListAdapter(navigator)
offlineAreaListAdapter = OfflineAreaListAdapter()
viewModel.offlineAreas.observe(this) { offlineAreaListAdapter.update(it) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
*/
package com.google.android.ground.ui.offlineareas

import android.view.View
import androidx.lifecycle.LiveData
import androidx.lifecycle.toLiveData
import com.google.android.ground.model.imagery.OfflineArea
import com.google.android.ground.repository.OfflineAreaRepository
import com.google.android.ground.ui.common.AbstractViewModel
import com.google.android.ground.ui.common.Navigator
import com.google.android.ground.util.toMb
import com.google.android.ground.util.toMbString
import javax.inject.Inject
import kotlinx.coroutines.rx2.rxSingle
import timber.log.Timber

/**
Expand All @@ -32,34 +34,45 @@ class OfflineAreasViewModel
@Inject
internal constructor(
private val navigator: Navigator,
offlineAreaRepository: OfflineAreaRepository
private val offlineAreaRepository: OfflineAreaRepository
) : AbstractViewModel() {

/**
* Returns the current list of downloaded offline map imagery areas available for viewing. If an
* unexpected error accessing the local store is encountered, emits an empty list, circumventing
* the error.
*/
val offlineAreas: LiveData<List<OfflineArea>>
val offlineAreas: LiveData<List<OfflineAreaListItemViewModel>>

/**
* Returns the visibility of the "no area" message based on the current number of available
* offline areas.
*/
val noAreasMessageVisibility: LiveData<Int>
val showHeader: LiveData<Boolean>
val showNoAreasMessage: LiveData<Boolean>
val showProgressSpinner: LiveData<Boolean>

init {
val offlineAreas =
offlineAreaRepository
.offlineAreasOnceAndStream()
.switchMapSingle { rxSingle { toViewModel(it) } }
.doOnError { Timber.e(it, "Unexpected error loading offline areas from the local db") }
.onErrorReturnItem(listOf())

this.offlineAreas = offlineAreas.toLiveData()
noAreasMessageVisibility =
offlineAreas.map { if (it.isEmpty()) View.VISIBLE else View.GONE }.toLiveData()
showProgressSpinner = offlineAreas.map { false }.startWith(true).toLiveData()
showNoAreasMessage = offlineAreas.map { it.isEmpty() }.toLiveData()
showHeader = offlineAreas.map { it.isNotEmpty() }.startWith(false).toLiveData()
}

private suspend fun toViewModel(
offlineAreas: List<OfflineArea>
): List<OfflineAreaListItemViewModel> = offlineAreas.map { toViewModel(it) }

private suspend fun toViewModel(offlineArea: OfflineArea) =
OfflineAreaListItemViewModel(
navigator,
offlineArea,
offlineAreaRepository.sizeOnDevice(offlineArea).toMb().toMbString()
)

/** Navigate to the area selector for offline map imagery. */
fun showOfflineAreaSelector() {
navigator.navigate(OfflineAreasFragmentDirections.showOfflineAreaSelector())
Expand Down
29 changes: 0 additions & 29 deletions ground/src/main/res/drawable-anydpi/ic_offline_pin.xml

This file was deleted.

27 changes: 27 additions & 0 deletions ground/src/main/res/drawable/ic_maps_ar.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>

<!--
~ Copyright 2023 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M0,5V3C0,2.167 0.292,1.458 0.875,0.875C1.458,0.292 2.167,-0 3,-0H5V2H3C2.717,2 2.475,2.1 2.275,2.3C2.092,2.483 2,2.717 2,3V5H0ZM3,22C2.167,22 1.458,21.708 0.875,21.125C0.292,20.542 0,19.833 0,19V17H2V19C2,19.283 2.092,19.525 2.275,19.725C2.475,19.908 2.717,20 3,20H5V22H3ZM17,22V20H19C19.283,20 19.517,19.908 19.7,19.725C19.9,19.525 20,19.283 20,19V17H22V19C22,19.833 21.708,20.542 21.125,21.125C20.542,21.708 19.833,22 19,22H17ZM20,5V3C20,2.717 19.9,2.483 19.7,2.3C19.517,2.1 19.283,2 19,2H17V-0H19C19.833,-0 20.542,0.292 21.125,0.875C21.708,1.458 22,2.167 22,3V5H20ZM11,15.3C11.283,14.817 11.567,14.375 11.85,13.975C12.15,13.575 12.442,13.183 12.725,12.8C13.292,12.05 13.742,11.383 14.075,10.8C14.425,10.217 14.6,9.483 14.6,8.6C14.6,7.6 14.25,6.75 13.55,6.05C12.85,5.35 12,5 11,5C10,5 9.15,5.35 8.45,6.05C7.75,6.75 7.4,7.6 7.4,8.6C7.4,9.483 7.567,10.217 7.9,10.8C8.25,11.383 8.708,12.05 9.275,12.8C9.558,13.183 9.842,13.575 10.125,13.975C10.425,14.375 10.717,14.817 11,15.3ZM11,19C10.817,19 10.65,18.95 10.5,18.85C10.367,18.733 10.275,18.592 10.225,18.425C9.858,17.325 9.4,16.4 8.85,15.65C8.3,14.883 7.767,14.158 7.25,13.475C6.75,12.775 6.317,12.058 5.95,11.325C5.583,10.575 5.4,9.667 5.4,8.6C5.4,7.033 5.942,5.708 7.025,4.625C8.108,3.542 9.433,3 11,3C12.567,3 13.892,3.542 14.975,4.625C16.058,5.708 16.6,7.033 16.6,8.6C16.6,9.667 16.417,10.575 16.05,11.325C15.683,12.058 15.242,12.775 14.725,13.475C14.225,14.158 13.7,14.883 13.15,15.65C12.6,16.4 12.142,17.325 11.775,18.425C11.725,18.592 11.625,18.733 11.475,18.85C11.342,18.95 11.183,19 11,19ZM11,10.6C11.55,10.6 12.017,10.408 12.4,10.025C12.8,9.625 13,9.15 13,8.6C13,8.05 12.8,7.583 12.4,7.2C12.017,6.8 11.55,6.6 11,6.6C10.45,6.6 9.975,6.8 9.575,7.2C9.192,7.583 9,8.05 9,8.6C9,9.15 9.192,9.625 9.575,10.025C9.975,10.408 10.45,10.6 11,10.6Z"
android:fillColor="#002108"/>
</vector>
2 changes: 1 addition & 1 deletion ground/src/main/res/layout/offline_area_viewer_frag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
tools:text="Area name, Country" />
<TextView
android:id="@+id/offline_area_size_on_device"
style="@style/TextAppearance.App.BodyMedium.OnSurface"
style="@style/TextAppearance.App.BodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
Expand Down
58 changes: 47 additions & 11 deletions ground/src/main/res/layout/offline_areas_frag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,48 +35,84 @@
android:id="@+id/offline_areas_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/ic_arrow_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_arrow_back"
app:title="@string/offline_map_imagery" />
<LinearLayout
android:id="@+id/offline_area_list_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/offline_areas_toolbar"
app:visible="@{viewModel.showHeader}">
<TextView
android:id="@+id/offline_areas_list_title"
style="@style/TextAppearance.App.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/offline_downloaded_areas" />
<TextView
android:id="@+id/offline_areas_list_tip"
style="@style/TextAppearance.App.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/offline_area_list_tip"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/offline_areas_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/offline_areas_toolbar" />
app:layout_constraintTop_toBottomOf="@+id/offline_area_list_heading" />

<Button
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/chip_button_margin_right"
android:layout_marginBottom="@dimen/map_btn_margin_bottom"
android:clickable="true"
android:contentDescription="@string/offline_area_selector_prompt"
android:focusable="true"
android:onClick="@{() -> viewModel.showOfflineAreaSelector()}"
android:text="@string/offline_area_selector_select"
app:icon="@drawable/ic_maps_ar"
app:iconTint="?attr/colorOnPrimaryContainer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/no_areas_downloaded_message"
style="@style/TextAppearance.App.BodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textAlignment="center"
style="@style/TextAppearance.App.BodyMedium.OnSurface"
android:contentDescription="@string/offline_area_selector_prompt"
android:padding="48dp"
android:text="@string/no_basemaps_downloaded"
android:visibility="@{viewModel.noAreasMessageVisibility}"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

app:layout_constraintTop_toTopOf="parent"
app:visible="@{viewModel.showNoAreasMessage}" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:visible="@{viewModel.showProgressSpinner}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Loading

0 comments on commit f6f28af

Please sign in to comment.