Skip to content

Commit

Permalink
Display coffee shops on the map
Browse files Browse the repository at this point in the history
  • Loading branch information
StrixG committed Dec 24, 2023
1 parent 1d731ac commit 5598779
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ interface CoffeeShopsRepository {
suspend fun refreshAll()

fun getCoffeeShopsStream(): Flow<List<CoffeeShop>>
suspend fun getCoffeeShopById(id: Long): CoffeeShop?
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ class DefaultCoffeeShopsRepository @Inject constructor(
}
}
}

override suspend fun getCoffeeShopById(id: Long): CoffeeShop? {
return coffeeShopsDao.getById(id)?.run {
CoffeeShop(id, name, point, point.latitude.toLong())
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package com.obrekht.coffeeshops.coffeeshops.ui.map

import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.obrekht.coffeeshops.R
import com.obrekht.coffeeshops.coffeeshops.ui.model.CoffeeShop
import com.obrekht.coffeeshops.databinding.FragmentCoffeeShopsMapBinding
import com.yandex.mapkit.MapKitFactory
import com.yandex.mapkit.geometry.Point
import com.yandex.mapkit.logo.Padding
import com.yandex.mapkit.map.CameraPosition
import com.yandex.mapkit.map.ClusterListener
import com.yandex.mapkit.map.ClusterizedPlacemarkCollection
import com.yandex.mapkit.map.Map
import com.yandex.mapkit.map.MapObjectTapListener
import com.yandex.mapkit.map.PlacemarkMapObject
import com.yandex.mapkit.map.TextStyle
import com.yandex.runtime.image.ImageProvider
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

@AndroidEntryPoint
class CoffeeShopsMapFragment : Fragment(R.layout.fragment_coffee_shops_map) {
Expand All @@ -20,27 +35,84 @@ class CoffeeShopsMapFragment : Fragment(R.layout.fragment_coffee_shops_map) {

private val viewModel: CoffeeShopsMapViewModel by viewModels()

private var map: Map? = null
private var placemarkCollection: ClusterizedPlacemarkCollection? = null
private var placemarkIconImageProvider: ImageProvider? = null

private val placemarkTapListener = MapObjectTapListener { mapObject, _ ->
if (mapObject is PlacemarkMapObject && mapObject.userData is Long) {
val coffeeShopId = mapObject.userData as Long
// TODO: Navigate to menu fragment
true
} else {
false
}
}

private val clusterListener = ClusterListener { cluster ->
placemarkIconImageProvider?.let {
cluster.appearance.apply {
setIcon(
ImageProvider.fromResource(
requireContext(), R.drawable.ic_coffee_shops_cluster
)
)
setText("${cluster.placemarks.size}", TextStyle().apply {
color = resources.getColor(R.color.dark_brown, context?.theme)
})
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentCoffeeShopsMapBinding.bind(view)

val map = binding.mapView.mapWindow.map
map = binding.mapView.mapWindow.map.apply {
placemarkCollection = mapObjects.addClusterizedPlacemarkCollection(clusterListener)
}
placemarkIconImageProvider = ImageProvider.fromResource(
requireContext(), R.drawable.ic_coffee_shop_placemark
)

ViewCompat.setOnApplyWindowInsetsListener(binding.mapView) { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())

map.logo.setPadding(Padding(0, insets.bottom))
map?.logo?.setPadding(Padding(0, insets.bottom))

WindowInsetsCompat.CONSUMED
}

with(binding) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
uiState.moveCameraTo?.let {
map?.move(
CameraPosition(
Point(it.latitude, it.longitude),
DEFAULT_ZOOM,
DEFAULT_ROTATION,
DEFAULT_TILT
)
)
viewModel.onCameraMoved()
}

map?.let {
placemarkCollection?.clear()
uiState.coffeeShops.forEach(::addCoffeeShopOnMap)
}
}
}
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null

map = null
placemarkCollection = null
placemarkIconImageProvider = null
}

override fun onStart() {
Expand All @@ -54,4 +126,30 @@ class CoffeeShopsMapFragment : Fragment(R.layout.fragment_coffee_shops_map) {
MapKitFactory.getInstance().onStop()
super.onStop()
}

private fun addCoffeeShopOnMap(coffeeShop: CoffeeShop) {
val placemarkCollection = placemarkCollection ?: return
val textColor = resources.getColor(R.color.brown, context?.theme)

placemarkCollection.addPlacemark().apply {
geometry = Point(coffeeShop.point.latitude, coffeeShop.point.longitude)
userData = coffeeShop.id
placemarkIconImageProvider?.let {
setIcon(it)
}
setText(coffeeShop.name, TextStyle().apply {
placement = TextStyle.Placement.BOTTOM
color = textColor
outlineColor = Color.TRANSPARENT
})
addTapListener(placemarkTapListener)
}
placemarkCollection.clusterPlacemarks(60.0, 15)
}

companion object {
private const val DEFAULT_ZOOM = 15f
private const val DEFAULT_ROTATION = 0f
private const val DEFAULT_TILT = 0f
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
package com.obrekht.coffeeshops.coffeeshops.ui.map

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.obrekht.coffeeshops.coffeeshops.data.model.CoffeeShopPoint
import com.obrekht.coffeeshops.coffeeshops.data.repository.CoffeeShopsRepository
import com.obrekht.coffeeshops.coffeeshops.ui.model.CoffeeShop
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class CoffeeShopsMapViewModel @Inject constructor(): ViewModel()
class CoffeeShopsMapViewModel @Inject constructor(
private val coffeeShopsRepository: CoffeeShopsRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {

private val args = CoffeeShopsMapFragmentArgs.fromSavedStateHandle(savedStateHandle)

private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()

init {
if (args.coffeeShopId != 0L) {
viewModelScope.launch {
coffeeShopsRepository.getCoffeeShopById(args.coffeeShopId)?.let { coffeeShop ->
_uiState.update {
it.copy(moveCameraTo = coffeeShop.point)
}
}
}
}

viewModelScope.launch {
coffeeShopsRepository.getCoffeeShopsStream().collect { coffeeShops ->
_uiState.update {
it.copy(coffeeShops = coffeeShops)
}
}
}
}

fun onCameraMoved() {
_uiState.update {
it.copy(moveCameraTo = null)
}
}
}

data class UiState(
val coffeeShops: List<CoffeeShop> = emptyList(),
val moveCameraTo: CoffeeShopPoint? = null
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<item name="colorPrimary">@color/brown</item>
<item name="colorContainer">@color/beige</item>
<item name="colorOnContainer">@color/brown</item>
<item name="colorSurface">@color/white</item>
<item name="android:colorBackground">@color/white</item>
<item name="materialButtonStyle">@style/Widget.App.Button</item>
</style>
</resources>
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ hilt = "2.50"
room = "2.6.1"
okhttp-bom = "4.12.0"
retrofit = "2.10.0-SNAPSHOT"
yandex-mapkit = "4.4.0-lite"
yandex-mapkit = "4.5.0-lite"
# Tests
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
Expand Down

0 comments on commit 5598779

Please sign in to comment.