forked from organicmaps/organicmaps
android: make images offline
This commit is contained in:
parent
451d628bfd
commit
a3d2a22f5b
11 changed files with 432 additions and 9 deletions
|
@ -38,6 +38,7 @@
|
|||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<!--
|
||||
Android 13 (API level 33) and higher supports a runtime permission for sending non-exempt (including Foreground
|
||||
Services (FGS)) notifications from an app.
|
||||
|
@ -461,6 +462,12 @@
|
|||
android:foregroundServiceType="location"
|
||||
android:stopWithTask="false" />
|
||||
|
||||
<service
|
||||
android:name="app.tourism.ImagesDownloadService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false"
|
||||
/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${FILE_PROVIDER_PLACEHOLDER}"
|
||||
|
|
172
android/app/src/main/java/app/tourism/ImagesDownloadService.kt
Normal file
172
android/app/src/main/java/app/tourism/ImagesDownloadService.kt
Normal file
|
@ -0,0 +1,172 @@
|
|||
package app.tourism
|
||||
|
||||
import android.Manifest
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import app.organicmaps.R
|
||||
import app.tourism.data.prefs.UserPreferences
|
||||
import app.tourism.data.repositories.PlacesRepository
|
||||
import app.tourism.domain.models.resource.DownloadProgress
|
||||
import app.tourism.utils.LocaleHelper
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val STOP_SERVICE_ACTION = "app.tourism.STOP_SERVICE"
|
||||
private const val CHANNEL_ID = "images_download_channel"
|
||||
private const val PROGRESS_NOTIFICATION_ID = 1
|
||||
private const val SUMMARY_NOTIFICATION_ID = 2
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImagesDownloadService : LifecycleService() {
|
||||
|
||||
|
||||
@Inject
|
||||
lateinit var placesRepository: PlacesRepository
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
val languageCode = UserPreferences(newBase).getLanguage()?.code
|
||||
super.attachBaseContext(LocaleHelper.localeUpdateResources(newBase, languageCode ?: "ru"))
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
notificationManager = NotificationManagerCompat.from(this)
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
// Stops the service when cancel button is clicked
|
||||
if (intent?.action == STOP_SERVICE_ACTION) {
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
// downloading all images
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
placesRepository.downloadAllImages().collectLatest { progress ->
|
||||
updateNotification(progress)
|
||||
}
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name = getString(R.string.channel_name)
|
||||
val descriptionText = getString(R.string.channel_description)
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
|
||||
description = descriptionText
|
||||
}
|
||||
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateNotification(downloadProgress: DownloadProgress) {
|
||||
var shouldStopSelf = false
|
||||
|
||||
val stopIntent = Intent(this, ImagesDownloadService::class.java).apply {
|
||||
action = STOP_SERVICE_ACTION
|
||||
}
|
||||
val stopPendingIntent = PendingIntent.getService(
|
||||
this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_download)
|
||||
.setContentTitle(getString(R.string.downloading_images))
|
||||
.setSilent(true)
|
||||
.addAction(R.drawable.ic_cancel, getString(R.string.cancel), stopPendingIntent)
|
||||
|
||||
val groupKey = "images_download_group"
|
||||
val summaryNotification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_download)
|
||||
.setContentTitle(getString(R.string.downloading_images))
|
||||
.setStyle(NotificationCompat.InboxStyle())
|
||||
.setGroup(groupKey)
|
||||
.setGroupSummary(true)
|
||||
|
||||
when (downloadProgress) {
|
||||
is DownloadProgress.Loading -> {
|
||||
downloadProgress.stats?.let { stats ->
|
||||
val statsInString =
|
||||
"${stats.filesDownloaded}/${stats.filesTotalNum} (${stats.percentagesCompleted}%)"
|
||||
|
||||
builder.setContentText("${getString(R.string.images_downloaded)}: $statsInString")
|
||||
builder.setProgress(100, stats.percentagesCompleted, false)
|
||||
}
|
||||
}
|
||||
|
||||
is DownloadProgress.Finished -> {
|
||||
downloadProgress.stats?.let { stats ->
|
||||
val statsInString =
|
||||
"${stats.filesDownloaded}/${stats.filesTotalNum} (${stats.percentagesCompleted}%)"
|
||||
|
||||
if (stats.percentagesCompleted == 100) {
|
||||
summaryNotification.setContentTitle("${getString(R.string.all_images_were_downloaded)}: $statsInString")
|
||||
summaryNotification.setContentText(null)
|
||||
} else if (stats.percentagesCompleted >= 95) {
|
||||
summaryNotification.setContentTitle("${getString(R.string.most_images_were_downloaded)}: $statsInString")
|
||||
summaryNotification.setContentText(null)
|
||||
} else {
|
||||
summaryNotification.setContentTitle("${getString(R.string.not_all_images_were_downloaded)}: $statsInString")
|
||||
summaryNotification.setContentText(null)
|
||||
}
|
||||
}
|
||||
notificationManager.cancel(PROGRESS_NOTIFICATION_ID)
|
||||
shouldStopSelf = true
|
||||
}
|
||||
|
||||
is DownloadProgress.Error -> {
|
||||
summaryNotification.setContentTitle(
|
||||
downloadProgress.message ?: getString(R.string.smth_went_wrong)
|
||||
)
|
||||
summaryNotification.setContentText("")
|
||||
summaryNotification.setProgress(0, 0, false)
|
||||
|
||||
notificationManager.cancel(PROGRESS_NOTIFICATION_ID)
|
||||
shouldStopSelf = true
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val notificationPermission =
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||
|
||||
if (notificationPermission != PackageManager.PERMISSION_GRANTED)
|
||||
return
|
||||
}
|
||||
|
||||
if (shouldStopSelf) {
|
||||
lifecycleScope.launch {
|
||||
notificationManager.notify(SUMMARY_NOTIFICATION_ID, summaryNotification.build())
|
||||
delay(1000L) // Delay to ensure notification is shown
|
||||
stopSelf()
|
||||
}
|
||||
} else {
|
||||
notificationManager.notify(PROGRESS_NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,13 @@ import androidx.room.RoomDatabase
|
|||
import androidx.room.TypeConverters
|
||||
import app.tourism.data.db.dao.CurrencyRatesDao
|
||||
import app.tourism.data.db.dao.HashesDao
|
||||
import app.tourism.data.db.dao.ImagesToDownloadDao
|
||||
import app.tourism.data.db.dao.PlacesDao
|
||||
import app.tourism.data.db.dao.ReviewsDao
|
||||
import app.tourism.data.db.entities.CurrencyRatesEntity
|
||||
import app.tourism.data.db.entities.FavoriteSyncEntity
|
||||
import app.tourism.data.db.entities.HashEntity
|
||||
import app.tourism.data.db.entities.ImageToDownloadEntity
|
||||
import app.tourism.data.db.entities.PlaceEntity
|
||||
import app.tourism.data.db.entities.ReviewEntity
|
||||
import app.tourism.data.db.entities.ReviewPlannedToPostEntity
|
||||
|
@ -21,7 +23,8 @@ import app.tourism.data.db.entities.ReviewPlannedToPostEntity
|
|||
ReviewPlannedToPostEntity::class,
|
||||
FavoriteSyncEntity::class,
|
||||
HashEntity::class,
|
||||
CurrencyRatesEntity::class
|
||||
CurrencyRatesEntity::class,
|
||||
ImageToDownloadEntity::class
|
||||
],
|
||||
version = 2,
|
||||
exportSchema = false
|
||||
|
@ -32,4 +35,5 @@ abstract class Database : RoomDatabase() {
|
|||
abstract val placesDao: PlacesDao
|
||||
abstract val hashesDao: HashesDao
|
||||
abstract val reviewsDao: ReviewsDao
|
||||
abstract val imagesToDownloadDao: ImagesToDownloadDao
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package app.tourism.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import app.tourism.data.db.entities.ImageToDownloadEntity
|
||||
|
||||
@Dao
|
||||
interface ImagesToDownloadDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertImages(places: List<ImageToDownloadEntity>)
|
||||
|
||||
@Query("UPDATE images_to_download SET downloaded = :downloaded WHERE url = :url")
|
||||
suspend fun markAsDownloaded(url: String, downloaded: Boolean)
|
||||
|
||||
@Query("UPDATE images_to_download SET downloaded = 0")
|
||||
suspend fun markAllImagesAsNotDownloaded()
|
||||
|
||||
@Query("SELECT * FROM images_to_download")
|
||||
suspend fun getAllImages(): List<ImageToDownloadEntity>
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package app.tourism.data.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "images_to_download")
|
||||
data class ImageToDownloadEntity(
|
||||
@PrimaryKey
|
||||
val url: String,
|
||||
val downloaded: Boolean
|
||||
)
|
|
@ -6,6 +6,7 @@ import app.organicmaps.R
|
|||
import app.tourism.data.db.Database
|
||||
import app.tourism.data.db.entities.FavoriteSyncEntity
|
||||
import app.tourism.data.db.entities.HashEntity
|
||||
import app.tourism.data.db.entities.ImageToDownloadEntity
|
||||
import app.tourism.data.db.entities.PlaceEntity
|
||||
import app.tourism.data.db.entities.ReviewEntity
|
||||
import app.tourism.data.dto.FavoritesIdsDto
|
||||
|
@ -18,7 +19,13 @@ import app.tourism.domain.models.SimpleResponse
|
|||
import app.tourism.domain.models.categories.PlaceCategory
|
||||
import app.tourism.domain.models.common.PlaceShort
|
||||
import app.tourism.domain.models.details.PlaceFull
|
||||
import app.tourism.domain.models.resource.DownloadProgress
|
||||
import app.tourism.domain.models.resource.DownloadStats
|
||||
import app.tourism.domain.models.resource.Resource
|
||||
import coil.imageLoader
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
|
@ -34,6 +41,8 @@ class PlacesRepository(
|
|||
private val placesDao = db.placesDao
|
||||
private val reviewsDao = db.reviewsDao
|
||||
private val hashesDao = db.hashesDao
|
||||
private val imagesToDownloadDao = db.imagesToDownloadDao
|
||||
|
||||
private val language = userPreferences.getLanguage()?.code ?: "ru"
|
||||
|
||||
fun downloadAllData(): Flow<Resource<SimpleResponse>> = flow {
|
||||
|
@ -90,14 +99,38 @@ class PlacesRepository(
|
|||
placeDto.toEntity(PlaceCategory.Hotels, "ru")
|
||||
}
|
||||
|
||||
val allPlacesEntities =
|
||||
sightsEntitiesEn + restaurantsEntitiesEn + hotelsEntitiesEn + sightsEntitiesRu + restaurantsEntitiesRu + hotelsEntitiesRu
|
||||
|
||||
// add all images urls to download
|
||||
val imagesToDownload = mutableListOf<ImageToDownloadEntity>()
|
||||
allPlacesEntities.forEach { placeEntity ->
|
||||
val gallery = placeEntity.gallery.filter { it.isNotBlank() }
|
||||
.map { ImageToDownloadEntity(it, false) }
|
||||
imagesToDownload.addAll(gallery)
|
||||
if (placeEntity.cover.isNotBlank()) {
|
||||
val cover = ImageToDownloadEntity(placeEntity.cover, false)
|
||||
imagesToDownload.add(cover)
|
||||
}
|
||||
}
|
||||
reviewsEntities.forEach { reviewEntity ->
|
||||
val images = reviewEntity.images.filter { it.isNotBlank() }
|
||||
.map { ImageToDownloadEntity(it, false) }
|
||||
imagesToDownload.addAll(images)
|
||||
|
||||
reviewEntity.user.avatar?.let {
|
||||
if (it.isNotBlank()) {
|
||||
val userPfp = ImageToDownloadEntity(it, false)
|
||||
imagesToDownload.add(userPfp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imagesToDownloadDao.insertImages(imagesToDownload)
|
||||
|
||||
// update places
|
||||
placesDao.deleteAllPlaces()
|
||||
placesDao.insertPlaces(sightsEntitiesEn)
|
||||
placesDao.insertPlaces(restaurantsEntitiesEn)
|
||||
placesDao.insertPlaces(hotelsEntitiesEn)
|
||||
placesDao.insertPlaces(sightsEntitiesRu)
|
||||
placesDao.insertPlaces(restaurantsEntitiesRu)
|
||||
placesDao.insertPlaces(hotelsEntitiesRu)
|
||||
placesDao.insertPlaces(allPlacesEntities)
|
||||
|
||||
// update reviews
|
||||
reviewsDao.deleteAllReviews()
|
||||
|
@ -127,6 +160,82 @@ class PlacesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
fun downloadAllImages(): Flow<DownloadProgress> = flow {
|
||||
try {
|
||||
val imagesToDownload = imagesToDownloadDao.getAllImages()
|
||||
val notDownloadedImages = imagesToDownload.filter { !it.downloaded }
|
||||
|
||||
val filesTotalNum = imagesToDownload.size
|
||||
val filesDownloaded = filesTotalNum - notDownloadedImages.size
|
||||
val downloadStats = DownloadStats(
|
||||
filesTotalNum,
|
||||
filesDownloaded,
|
||||
0
|
||||
)
|
||||
|
||||
if (downloadStats.percentagesCompleted >= 90) return@flow
|
||||
|
||||
notDownloadedImages.forEach {
|
||||
try {
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(it.url)
|
||||
.build()
|
||||
val result = context.imageLoader.execute(request)
|
||||
|
||||
when (result) {
|
||||
is SuccessResult -> {
|
||||
downloadStats.filesDownloaded++
|
||||
imagesToDownloadDao.markAsDownloaded(it.url, true)
|
||||
}
|
||||
|
||||
is ErrorResult -> {
|
||||
downloadStats.filesFailedToDownload++
|
||||
Log.d("", "Url failed to download: ${it.url}")
|
||||
}
|
||||
}
|
||||
|
||||
downloadStats.updatePercentage()
|
||||
Log.d("", "downloadStats: $downloadStats")
|
||||
|
||||
if(downloadStats.isAllFilesProcessed()) {
|
||||
emit(DownloadProgress.Finished(downloadStats))
|
||||
} else {
|
||||
emit(DownloadProgress.Loading(downloadStats))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
downloadStats.filesFailedToDownload++
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
emit(DownloadProgress.Error(message = context.getString(R.string.smth_went_wrong)))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun markAllImagesAsNotDownloadedIfCacheWasCleared() {
|
||||
// if coil cache is less than 10 MB,
|
||||
// then most likely it was cleared and data needs to be downloaded again
|
||||
// so we mark all images as not downloaded
|
||||
context.imageLoader.diskCache?.let {
|
||||
if (it.size < 10000000) {
|
||||
imagesToDownloadDao.markAllImagesAsNotDownloaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun shouldDownloadImages(): Boolean {
|
||||
val imagesToDownload = imagesToDownloadDao.getAllImages()
|
||||
val notDownloadedImages = imagesToDownload.filter { !it.downloaded }
|
||||
|
||||
val filesTotalNum = imagesToDownload.size
|
||||
val filesDownloaded = filesTotalNum - notDownloadedImages.size
|
||||
val percentage = (filesDownloaded * 100) / filesTotalNum
|
||||
|
||||
Log.d("", "percentage: $percentage")
|
||||
return percentage < 90
|
||||
}
|
||||
|
||||
fun search(q: String): Flow<Resource<List<PlaceShort>>> = channelFlow {
|
||||
placesDao.search("%$q%", language).collectLatest { placeEntities ->
|
||||
val places = placeEntities.map { it.toPlaceShort() }
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package app.tourism.domain.models.resource
|
||||
|
||||
sealed class DownloadProgress(val stats: DownloadStats? = null, val message: String? = null) {
|
||||
class Idle : DownloadProgress()
|
||||
class Loading(stats: DownloadStats) : DownloadProgress(stats)
|
||||
class Finished(stats: DownloadStats, message: String? = null) : DownloadProgress(stats, message)
|
||||
class Error(message: String) : DownloadProgress(message = message)
|
||||
}
|
||||
|
||||
class DownloadStats(
|
||||
val filesTotalNum: Int,
|
||||
var filesDownloaded: Int,
|
||||
var filesFailedToDownload: Int,
|
||||
) {
|
||||
var percentagesCompleted: Int = 0
|
||||
|
||||
init {
|
||||
updatePercentage()
|
||||
}
|
||||
|
||||
fun updatePercentage() {
|
||||
percentagesCompleted = calculatePercentage()
|
||||
}
|
||||
|
||||
fun isAllFilesProcessed() =
|
||||
filesTotalNum == filesDownloaded + filesFailedToDownload
|
||||
|
||||
|
||||
private fun calculatePercentage(): Int {
|
||||
return if (filesTotalNum == 0) 0 else (filesDownloaded * 100) / filesTotalNum
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "DownloadStats(percentagesCompleted=$percentagesCompleted, filesDownloaded=$filesDownloaded, filesTotalNum=$filesTotalNum, filesFailedToDownload=$filesFailedToDownload)"
|
||||
}
|
||||
}
|
|
@ -41,6 +41,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import app.organicmaps.R
|
||||
import app.tourism.Constants
|
||||
import app.tourism.domain.models.common.PlaceShort
|
||||
|
@ -62,6 +65,9 @@ import app.tourism.ui.screens.main.categories.categories.HorizontalSingleChoice
|
|||
import app.tourism.ui.theme.TextStyles
|
||||
import app.tourism.ui.theme.getStarColor
|
||||
import app.tourism.ui.utils.showToast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
|
@ -111,6 +117,8 @@ fun HomeScreen(
|
|||
.padding(paddingValues)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
StartImagesDownloadIfNecessary(homeVM)
|
||||
|
||||
Column(Modifier.padding(horizontal = Constants.SCREEN_PADDING)) {
|
||||
VerticalSpace(height = 16.dp)
|
||||
|
||||
|
@ -300,3 +308,17 @@ private fun Place(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StartImagesDownloadIfNecessary(homeVM: HomeViewModel) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
LaunchedEffect(Unit, lifecycleOwner) {
|
||||
// this delay is here because it might navigate to map to download it
|
||||
delay(3000L)
|
||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
homeVM.startDownloadServiceIfNecessary()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
package app.tourism.ui.screens.main.home
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.organicmaps.util.log.Logger
|
||||
import app.organicmaps.R
|
||||
import app.tourism.ImagesDownloadService
|
||||
import app.tourism.data.repositories.PlacesRepository
|
||||
import app.tourism.domain.models.SimpleResponse
|
||||
import app.tourism.domain.models.categories.PlaceCategory
|
||||
|
@ -23,11 +24,14 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class HomeViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val placesRepository: PlacesRepository
|
||||
) : ViewModel() {
|
||||
private val uiChannel = Channel<UiEvent>()
|
||||
val uiEventsChannelFlow = uiChannel.receiveAsFlow()
|
||||
|
||||
private val _downloadServiceAlreadyRunning = MutableStateFlow(false)
|
||||
|
||||
// region search query
|
||||
private val _query = MutableStateFlow("")
|
||||
val query = _query.asStateFlow()
|
||||
|
@ -81,6 +85,12 @@ class HomeViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun markAllImagesAsNotDownloadedIfCacheWasCleared() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
placesRepository.markAllImagesAsNotDownloadedIfCacheWasCleared()
|
||||
}
|
||||
}
|
||||
|
||||
private val _downloadResponse = MutableStateFlow<Resource<SimpleResponse>>(Resource.Idle())
|
||||
val downloadResponse = _downloadResponse.asStateFlow()
|
||||
private fun downloadAllData() {
|
||||
|
@ -91,6 +101,19 @@ class HomeViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun startDownloadServiceIfNecessary() {
|
||||
if (!_downloadServiceAlreadyRunning.value) {
|
||||
_downloadServiceAlreadyRunning.value = true
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (placesRepository.shouldDownloadImages()) {
|
||||
uiChannel.send(UiEvent.ShowToast(context.getString(R.string.downloading_images)))
|
||||
val intent = Intent(context, ImagesDownloadService::class.java)
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setFavoriteChanged(item: PlaceShort, isFavorite: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
placesRepository.setFavorite(item.id, isFavorite)
|
||||
|
@ -98,6 +121,7 @@ class HomeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
markAllImagesAsNotDownloadedIfCacheWasCleared()
|
||||
downloadAllData()
|
||||
getTopSights()
|
||||
getTopRestaurants()
|
||||
|
|
|
@ -2234,4 +2234,12 @@
|
|||
<string name="review_was_published">Отзыв был успешно опубликован</string>
|
||||
<string name="failed_to_publish_review">Не удалось публиковать отзыв</string>
|
||||
<string name="plz_dont_go_out_of_tjk">Поажалуйста, не выходите за рамки Таджикистана, вы должны быть в Таджикистане</string>
|
||||
<string name="channel_name">Загрузка изображений</string>
|
||||
<string name="channel_description">Загрузка изображений для оффлайн использования</string>
|
||||
<string name="downloading_images">Загрузка изображений</string>
|
||||
<string name="images_downloaded">Изображений загружено</string>
|
||||
<string name="images_download_failed">Ошибка загрузки</string>
|
||||
<string name="all_images_were_downloaded">Все изображения былы загружены успешно</string>
|
||||
<string name="most_images_were_downloaded">Большинство изображений было загружено</string>
|
||||
<string name="not_all_images_were_downloaded">Ошибка, не все изображения былы загружены</string>
|
||||
</resources>
|
||||
|
|
|
@ -2276,4 +2276,12 @@
|
|||
<string name="review_was_published">Review was successfully published</string>
|
||||
<string name="failed_to_publish_review">Failed to publish review\n</string>
|
||||
<string name="plz_dont_go_out_of_tjk">Please, don\'t go out of Tajikistan, it\'s Tajikistan app</string>
|
||||
<string name="channel_name">Downloading images</string>
|
||||
<string name="channel_description">Downloading images for offline usage</string>
|
||||
<string name="downloading_images">Downloading images</string>
|
||||
<string name="images_downloaded">Images downloaded</string>
|
||||
<string name="images_download_failed">Download failed</string>
|
||||
<string name="all_images_were_downloaded">All images were downloaded successfully</string>
|
||||
<string name="most_images_were_downloaded">Most images were downloaded</string>
|
||||
<string name="not_all_images_were_downloaded">Error, not all images were downloaded</string>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue