Skip to content

Commit

Permalink
[SDK-#] add download territories sample
Browse files Browse the repository at this point in the history
  • Loading branch information
v.lazin authored and Sameri11 committed Aug 19, 2024
1 parent af63376 commit e40ac66
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 0 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
android:name=".SearchActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".DownloadTerritoriesActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustNothing" />
</application>

</manifest>
201 changes: 201 additions & 0 deletions app/src/main/java/ru/dgis/sdk/demo/DownloadTerritoriesActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package ru.dgis.sdk.demo

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ProgressBar
import android.widget.SearchView
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import ru.dgis.sdk.demo.common.asFlow
import ru.dgis.sdk.demo.databinding.ActivityDownloadTerritoriesBinding
import ru.dgis.sdk.demo.vm.DownloadTerritoriesViewModel
import ru.dgis.sdk.demo.vm.Geometry
import ru.dgis.sdk.map.Map
import ru.dgis.sdk.update.PackageUpdateStatus
import ru.dgis.sdk.update.Territory

private class TerritoryViewHolder(
view: View,
private val scope: CoroutineScope
) : RecyclerView.ViewHolder(view) {
private var job: Job? = null
private var territory: Territory? = null

private val name = view.findViewById<TextView>(R.id.pkgName)!!
private val progressBar = view.findViewById<ProgressBar>(R.id.pkgProgressBar)!!
private val installButton = view.findViewById<ImageButton>(R.id.pkgInstallButton)!!
private val uninstallButton = view.findViewById<ImageButton>(R.id.pkgUninstallButton)!!

init {
progressBar.max = 100

installButton.setOnClickListener {
territory?.install()
}

uninstallButton.setOnClickListener {
territory?.uninstall()
}
}

fun setTerritory(territory: Territory) {
job?.cancel()

job = scope.launch {
launch {
territory.progressChannel.asFlow().collect {
progressBar.progress = it.toInt()
}
}

launch {
territory.infoChannel.asFlow().collect {
name.text = it.name
progressBar.isVisible = it.updateStatus == PackageUpdateStatus.IN_PROGRESS
installButton.isVisible = it.updateStatus == PackageUpdateStatus.PAUSED
uninstallButton.isVisible = it.installed
}
}
}

this.territory = territory
}
}

private class TerritoriesAdapter(
private val territory: List<Territory>,
private val scope: CoroutineScope
) : RecyclerView.Adapter<TerritoryViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TerritoryViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.territories_list_item, parent, false)
return TerritoryViewHolder(view, scope)
}

override fun getItemCount() = territory.size

override fun onBindViewHolder(holder: TerritoryViewHolder, position: Int) {
holder.setTerritory(territory[position])
}
}

/**
* This Activity demonstrates how to use the TerritoryManager to retrieve and display territories.
*
* This sample includes three modes for listing territories:
* 1. All territories.
* 2. Territories filtered by the current camera position.
* 3. Territories filtered by the current viewport.
*/
class DownloadTerritoriesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDownloadTerritoriesBinding
private val viewModel: DownloadTerritoriesViewModel by viewModels()
private var geometryFilterJob: Job? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityDownloadTerritoriesBinding.inflate(layoutInflater)

binding.territoriesRecycleView.adapter = TerritoriesAdapter(listOf(), lifecycleScope)

binding.searchView.setOnQueryTextFocusChangeListener(object : View.OnFocusChangeListener {
private val bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheet)
private var previousBottomSheetState = bottomSheetBehavior.state

override fun onFocusChange(view: View?, hasFocus: Boolean) {
bottomSheetBehavior.isDraggable = hasFocus.not()

if (hasFocus) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
} else if (previousBottomSheetState != BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}

previousBottomSheetState = bottomSheetBehavior.state
}
})

binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}

override fun onQueryTextChange(newText: String?): Boolean {
viewModel.nameFilter = newText
return false
}
})

binding.mapView.getMapAsync { map ->
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.territories.collect { territories ->
binding.territoriesRecycleView.adapter = TerritoriesAdapter(territories, lifecycleScope)
}
}
}

binding.radioGroupFilters.setOnCheckedChangeListener { _, checkedId ->
geometryFilterJob?.cancel()

geometryFilterJob = when (checkedId) {
R.id.radioButtonFilterByPosition -> {
startFilterByGeoPoint(map)
}

R.id.radioButtonFilterByViewport -> {
startFilterByGeoRect(map)
}

else -> {
viewModel.geometryFilter = null
return@setOnCheckedChangeListener
}
}
}
}

setContentView(binding.root)
}

@OptIn(FlowPreview::class)
private fun startFilterByGeoPoint(map: Map) = lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
map.camera.positionChannel
.asFlow()
.debounce(512)
.collect {
viewModel.geometryFilter = Geometry.Point(it.point)
}
}
}

@OptIn(FlowPreview::class)
private fun startFilterByGeoRect(map: Map) = lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
map.camera.visibleRectChannel
.asFlow()
.debounce(512)
.collect {
viewModel.geometryFilter = Geometry.Rect(it)
}
}
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ class MainActivity : AppCompatActivity() {
Page("Take Map Snapshot") {
val intent = Intent(this@MainActivity, TakeSnapshotActivity::class.java)
startActivity(intent)
},
Page("Download Territories") {
val intent = Intent(this@MainActivity, DownloadTerritoriesActivity::class.java)
startActivity(intent)
}
)

Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/ru/dgis/sdk/demo/common/Common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import android.widget.LinearLayout
import androidx.core.view.children
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import ru.dgis.sdk.StatefulChannel
import ru.dgis.sdk.demo.R
import ru.dgis.sdk.demo.common.views.SettingsLayoutView
import ru.dgis.sdk.map.MapView
Expand Down Expand Up @@ -47,3 +53,13 @@ fun ViewBinding.addSettingsLayout(init: ViewGroup.() -> Unit): SettingsLayoutVie
// Helper method for search mapView in hierarchy.
private val ViewGroup.mapView: MapView?
get() = children.find { it is MapView } as? MapView

fun <T : Any?> StatefulChannel<T>.asFlow(): Flow<T> = callbackFlow {
val connection = connect { value ->
trySend(value)
}

awaitClose {
connection.close()
}
}.buffer(Channel.CONFLATED)
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package ru.dgis.sdk.demo.vm

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import ru.dgis.sdk.DGis
import ru.dgis.sdk.coordinates.GeoPoint
import ru.dgis.sdk.coordinates.GeoRect
import ru.dgis.sdk.demo.common.asFlow
import ru.dgis.sdk.update.Territory
import ru.dgis.sdk.update.getTerritoryManager

sealed class Geometry {
data class Point(val geoPoint: GeoPoint) : Geometry()
data class Rect(val geoRect: GeoRect) : Geometry()
}

class DownloadTerritoriesViewModel : ViewModel() {
private val territoryManager = getTerritoryManager(DGis.context())

private val _territories = MutableStateFlow(listOf<Territory>())
val territories = _territories.asStateFlow()

private fun sortTerritories(territories: List<Territory>): List<Territory> {
return territories.sortedWith(
compareBy<Territory> { territory -> !territory.info.installed }
.thenBy { territory -> territory.info.name }
)
}

private fun getTerritories(
nameFilter: String?,
geometryFilter: Geometry?
): List<Territory> {
var territories = when (geometryFilter) {
is Geometry.Point ->
territoryManager.findByPoint(geometryFilter.geoPoint)

is Geometry.Rect ->
territoryManager.findByRect(geometryFilter.geoRect)

null -> territoryManager.territories
}

if (nameFilter != null) {
val query = nameFilter.toString().trim().lowercase()
territories = territories.filter { it.info.name.lowercase().contains(query) }
}

return sortTerritories(territories)
}

var geometryFilter: Geometry? = null
set(value) {
if (value == field) {
return
}
field = value

_territories.value = getTerritories(nameFilter, value)
}

var nameFilter: String? = null
set(value) {
if (value == field) {
return
}
field = value

_territories.value = getTerritories(value, geometryFilter)
}

init {
viewModelScope.launch {
territoryManager.territoriesChannel.asFlow().collect {
_territories.value = getTerritories(nameFilter, geometryFilter)
}
}
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/outline_delete_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M16,9v10H8V9h8m-1.5,-6h-5l-1,1H5v2h14V4h-3.5l-1,-1zM18,7H6v12c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/outline_file_download_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,15v3H6v-3H4v3c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-3H18zM17,11l-1.41,-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5L17,11z"/>
</vector>
Loading

0 comments on commit e40ac66

Please sign in to comment.