Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDK-#] add download territories sample #223

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading