diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListAdapter.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListAdapter.kt index 66f5849185..7365e79a26 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListAdapter.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListAdapter.kt @@ -15,51 +15,33 @@ */ 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) : - RecyclerView.Adapter() { +internal class OfflineAreaListAdapter : RecyclerView.Adapter() { - private var offlineAreas: List = listOf() + private var offlineAreas: List = listOf() - class ViewHolder - internal constructor( - var binding: OfflineAreasListItemBinding, - var areas: List, - 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) { + @SuppressLint("NotifyDataSetChanged") + fun update(offlineAreas: List) { this.offlineAreas = offlineAreas notifyDataSetChanged() } diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListItemViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListItemViewModel.kt new file mode 100644 index 0000000000..f6f94616e7 --- /dev/null +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreaListItemViewModel.kt @@ -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)) + } +} diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasFragment.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasFragment.kt index 8064b19bca..bdad68e720 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasFragment.kt @@ -20,7 +20,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.ground.MainActivity import com.google.android.ground.databinding.OfflineAreasFragBinding import com.google.android.ground.ui.common.AbstractFragment import com.google.android.ground.ui.common.Navigator @@ -43,7 +42,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) } } @@ -57,7 +56,8 @@ class OfflineAreasFragment : Hilt_OfflineAreasFragment() { binding.viewModel = viewModel binding.lifecycleOwner = this - (requireActivity() as MainActivity).setActionBar(binding.offlineAreasToolbar, true) + val toolbar = binding.offlineAreasToolbar + toolbar.setNavigationOnClickListener { navigator.navigateUp() } val recyclerView = binding.offlineAreasList recyclerView.setHasFixedSize(true) diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasViewModel.kt index 9ad951ec0f..444c1855b8 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/OfflineAreasViewModel.kt @@ -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 /** @@ -32,7 +34,7 @@ class OfflineAreasViewModel @Inject internal constructor( private val navigator: Navigator, - offlineAreaRepository: OfflineAreaRepository + private val offlineAreaRepository: OfflineAreaRepository ) : AbstractViewModel() { /** @@ -40,26 +42,37 @@ internal constructor( * unexpected error accessing the local store is encountered, emits an empty list, circumventing * the error. */ - val offlineAreas: LiveData> + val offlineAreas: LiveData> - /** - * Returns the visibility of the "no area" message based on the current number of available - * offline areas. - */ - val noAreasMessageVisibility: LiveData + val showList: LiveData + val showNoAreasMessage: LiveData + val showProgressSpinner: LiveData 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() }.startWith(false).toLiveData() + showList = offlineAreas.map { it.isNotEmpty() }.startWith(false).toLiveData() } + private suspend fun toViewModel( + offlineAreas: List + ): List = 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()) diff --git a/ground/src/main/res/drawable-anydpi/ic_offline_pin.xml b/ground/src/main/res/drawable-anydpi/ic_arrow_back.xml similarity index 82% rename from ground/src/main/res/drawable-anydpi/ic_offline_pin.xml rename to ground/src/main/res/drawable-anydpi/ic_arrow_back.xml index c6b42577ad..5a46179638 100644 --- a/ground/src/main/res/drawable-anydpi/ic_offline_pin.xml +++ b/ground/src/main/res/drawable-anydpi/ic_arrow_back.xml @@ -22,8 +22,9 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="#333333" - android:alpha="0.6"> + android:alpha="1.0" + android:autoMirrored="true"> + android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/> diff --git a/ground/src/main/res/drawable-anydpi/ic_edit_black.xml b/ground/src/main/res/drawable-anydpi/ic_edit_black.xml deleted file mode 100644 index 79307eb0a9..0000000000 --- a/ground/src/main/res/drawable-anydpi/ic_edit_black.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/ground/src/main/res/drawable/ic_maps_ar.xml b/ground/src/main/res/drawable/ic_maps_ar.xml new file mode 100644 index 0000000000..11f6bde20d --- /dev/null +++ b/ground/src/main/res/drawable/ic_maps_ar.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/ground/src/main/res/drawable/baseline_offline_pin_24.xml b/ground/src/main/res/drawable/ic_offline_pin.xml similarity index 100% rename from ground/src/main/res/drawable/baseline_offline_pin_24.xml rename to ground/src/main/res/drawable/ic_offline_pin.xml diff --git a/ground/src/main/res/layout/offline_area_selector_frag.xml b/ground/src/main/res/layout/offline_area_selector_frag.xml index 11ea47bf57..f75adc47d5 100644 --- a/ground/src/main/res/layout/offline_area_selector_frag.xml +++ b/ground/src/main/res/layout/offline_area_selector_frag.xml @@ -42,6 +42,7 @@ android:layout_height="wrap_content" android:elevation="@dimen/toolbar_elevation" android:minHeight="?attr/actionBarSize" + android:background="?attr/colorSurfaceContainer" app:layout_constraintTop_toTopOf="parent"> diff --git a/ground/src/main/res/layout/offline_area_viewer_frag.xml b/ground/src/main/res/layout/offline_area_viewer_frag.xml index 55d4b63234..b3ad2cd407 100644 --- a/ground/src/main/res/layout/offline_area_viewer_frag.xml +++ b/ground/src/main/res/layout/offline_area_viewer_frag.xml @@ -85,7 +85,7 @@ tools:text="Area name, Country" /> - + + + + + app:layout_constraintTop_toBottomOf="@+id/offline_area_list_heading" + app:visible="@{viewModel.showList}" /> -