mirror of
https://github.com/jahruz67/Ambient-Launcher.git
synced 2026-08-08 18:14:05 +00:00
160 lines
6.3 KiB
Kotlin
160 lines
6.3 KiB
Kotlin
package com.ambient.launcher
|
|
|
|
import android.app.Application
|
|
import android.content.ComponentName
|
|
import android.content.Intent
|
|
import android.content.pm.PackageManager
|
|
import androidx.documentfile.provider.DocumentFile
|
|
import androidx.lifecycle.AndroidViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.flow.collectLatest
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withContext
|
|
|
|
data class LauncherUiState(
|
|
val settings: LauncherSettings = LauncherSettings(),
|
|
val apps: List<LauncherApp> = emptyList(),
|
|
val localImages: List<Any> = emptyList(),
|
|
val weather: WeatherNow? = null,
|
|
val weatherLoading: Boolean = false,
|
|
val weatherError: String? = null
|
|
)
|
|
|
|
class LauncherViewModel(application: Application) : AndroidViewModel(application) {
|
|
private val store = SettingsStore(application)
|
|
private val weatherRepository = WeatherRepository()
|
|
private val _uiState = MutableStateFlow(LauncherUiState())
|
|
val uiState: StateFlow<LauncherUiState> = _uiState.asStateFlow()
|
|
|
|
init {
|
|
loadApps()
|
|
viewModelScope.launch {
|
|
store.settings.collectLatest { settings ->
|
|
val old = _uiState.value.settings
|
|
_uiState.value = _uiState.value.copy(settings = settings)
|
|
if (settings.localFolderUri != old.localFolderUri) loadLocalImages(settings.localFolderUri)
|
|
if (
|
|
settings.useFahrenheit != old.useFahrenheit ||
|
|
_uiState.value.weather == null
|
|
) {
|
|
refreshWeather()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun updateSettings(transform: (LauncherSettings) -> LauncherSettings) {
|
|
val updated = transform(_uiState.value.settings)
|
|
_uiState.value = _uiState.value.copy(settings = updated)
|
|
viewModelScope.launch { store.save(updated) }
|
|
}
|
|
|
|
fun togglePinned(app: LauncherApp) {
|
|
updateSettings { settings ->
|
|
val id = app.component.flattenToString()
|
|
val next = settings.pinnedComponents.toMutableSet().apply {
|
|
if (!add(id)) remove(id)
|
|
}
|
|
settings.copy(pinnedComponents = next)
|
|
}
|
|
}
|
|
|
|
fun setLocalFolder(uri: String) {
|
|
updateSettings { it.copy(localFolderUri = uri) }
|
|
loadLocalImages(uri)
|
|
}
|
|
|
|
fun refreshWeather() {
|
|
val settings = _uiState.value.settings
|
|
val lat = settings.latitude.toDoubleOrNull()
|
|
val lon = settings.longitude.toDoubleOrNull()
|
|
if (lat == null || lon == null || lat !in -90.0..90.0 || lon !in -180.0..180.0) {
|
|
_uiState.value = _uiState.value.copy(
|
|
weatherLoading = false,
|
|
weatherError = "Enter valid latitude and longitude."
|
|
)
|
|
return
|
|
}
|
|
viewModelScope.launch {
|
|
_uiState.value = _uiState.value.copy(weatherLoading = true, weatherError = null)
|
|
runCatching { weatherRepository.load(lat, lon, settings.useFahrenheit) }
|
|
.onSuccess {
|
|
_uiState.value = _uiState.value.copy(
|
|
weather = it,
|
|
weatherLoading = false,
|
|
weatherError = null
|
|
)
|
|
}
|
|
.onFailure {
|
|
_uiState.value = _uiState.value.copy(
|
|
weatherLoading = false,
|
|
weatherError = "Weather unavailable. Check the connection."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun launch(app: LauncherApp): Boolean {
|
|
val intent = Intent(Intent.ACTION_MAIN)
|
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
|
.setComponent(app.component)
|
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
|
|
return runCatching {
|
|
getApplication<Application>().startActivity(intent)
|
|
}.isSuccess
|
|
}
|
|
|
|
private fun loadApps() {
|
|
viewModelScope.launch {
|
|
val result = withContext(Dispatchers.IO) {
|
|
val context = getApplication<Application>()
|
|
val pm = context.packageManager
|
|
val intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER)
|
|
val flags = if (android.os.Build.VERSION.SDK_INT >= 33) {
|
|
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong())
|
|
} else null
|
|
val resolved = if (flags != null) {
|
|
pm.queryIntentActivities(intent, flags)
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
pm.queryIntentActivities(intent, PackageManager.MATCH_ALL)
|
|
}
|
|
resolved.mapNotNull { info ->
|
|
val activity = info.activityInfo ?: return@mapNotNull null
|
|
if (activity.packageName == context.packageName) return@mapNotNull null
|
|
LauncherApp(
|
|
label = info.loadLabel(pm).toString(),
|
|
component = ComponentName(activity.packageName, activity.name),
|
|
icon = info.loadIcon(pm)
|
|
)
|
|
}.distinctBy { it.component }.sortedBy { it.label.lowercase() }
|
|
}
|
|
_uiState.value = _uiState.value.copy(apps = result)
|
|
}
|
|
}
|
|
|
|
private fun loadLocalImages(uri: String) {
|
|
viewModelScope.launch {
|
|
val images = withContext(Dispatchers.IO) {
|
|
if (uri.isBlank()) return@withContext emptyList()
|
|
val context = getApplication<Application>()
|
|
runCatching {
|
|
DocumentFile.fromTreeUri(context, android.net.Uri.parse(uri))
|
|
?.listFiles()
|
|
?.asSequence()
|
|
?.filter { it.isFile && it.type?.startsWith("image/") == true }
|
|
?.mapNotNull { it.uri }
|
|
?.take(250)
|
|
?.toList()
|
|
?: emptyList()
|
|
}.getOrDefault(emptyList())
|
|
}
|
|
_uiState.value = _uiState.value.copy(localImages = images)
|
|
}
|
|
}
|
|
}
|