tawasuyu

sergio/humanoid

HTML 13TOML 10 Definitions

⎇ 8ab12b6800f7b28dfd3aa1a0ee97dff7b1ca41b7 launcher/src/main/java/com/rizoma/orbita/ui/OrbitaViewModel.kt 109 lines · 4.0 KB History raw

Órbita: alejar + fondo dinámico · Trazo: aprendizaje con umbral y bilingüe 8ab12b680 · sergio ·

1package com.rizoma.orbita.ui
2
3import android.app.Application
4import android.content.BroadcastReceiver
5import android.content.ComponentName
6import android.content.Context
7import android.content.Intent
8import android.content.IntentFilter
9import android.net.Uri
10import android.provider.Settings
11import androidx.lifecycle.AndroidViewModel
12import androidx.lifecycle.viewModelScope
13import com.rizoma.orbita.data.AppRepository
14import com.rizoma.orbita.data.UsageStore
15import com.rizoma.orbita.model.AppNode
16import kotlinx.coroutines.Dispatchers
17import kotlinx.coroutines.flow.MutableStateFlow
18import kotlinx.coroutines.flow.SharingStarted
19import kotlinx.coroutines.flow.StateFlow
20import kotlinx.coroutines.flow.combine
21import kotlinx.coroutines.flow.stateIn
22import kotlinx.coroutines.launch
23
24class OrbitaViewModel(app: Application) : AndroidViewModel(app) {
25
26    private val repo = AppRepository(app)
27    private val usage = UsageStore(app)
28
29    private val rawApps = MutableStateFlow<List<AppNode>>(emptyList())
30
31    /** Nodos ya ponderados y ordenados: fijados primero, luego por calor, luego alfabético. */
32    val nodes: StateFlow<List<AppNode>> = combine(
33        rawApps,
34        usage.counts(),
35        usage.pinned(),
36        usage.penalties(),
37    ) { apps, counts, pinned, penalties ->
38        apps.map { node ->
39            node.copy(
40                score = (counts[node.key] ?: 0).toFloat(),
41                pinned = node.key in pinned,
42            )
43        }.sortedWith(
44            // El orden define la órbita: fijados al centro, luego por calor efectivo
45            // (calor menos penalización por "alejar"), luego alfabético.
46            compareByDescending<AppNode> { it.pinned }
47                .thenByDescending { it.score - (penalties[it.key] ?: 0).toFloat() }
48                .thenBy { it.label.lowercase() }
49        )
50    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
51
52    private val pkgReceiver = object : BroadcastReceiver() {
53        override fun onReceive(context: Context?, intent: Intent?) = refresh()
54    }
55
56    init {
57        refresh()
58        val filter = IntentFilter().apply {
59            addAction(Intent.ACTION_PACKAGE_ADDED)
60            addAction(Intent.ACTION_PACKAGE_REMOVED)
61            addAction(Intent.ACTION_PACKAGE_CHANGED)
62            addDataScheme("package")
63        }
64        getApplication<Application>().registerReceiver(pkgReceiver, filter)
65    }
66
67    fun refresh() {
68        viewModelScope.launch(Dispatchers.IO) {
69            rawApps.value = repo.loadApps()
70        }
71    }
72
73    fun launch(node: AppNode) {
74        val intent = Intent(Intent.ACTION_MAIN)
75            .addCategory(Intent.CATEGORY_LAUNCHER)
76            .setComponent(ComponentName(node.packageName, node.activityName))
77            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
78        runCatching { getApplication<Application>().startActivity(intent) }
79        viewModelScope.launch { usage.record(node.key) }
80    }
81
82    fun togglePin(node: AppNode) {
83        viewModelScope.launch { usage.setPinned(node.key, !node.pinned) }
84    }
85
86    /** Aleja el nodo a órbitas externas (usa su calor actual como referencia). */
87    fun pushAway(node: AppNode) {
88        viewModelScope.launch { usage.pushAway(node.key, node.score.toInt()) }
89    }
90
91    fun openInfo(node: AppNode) {
92        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
93            .setData(Uri.fromParts("package", node.packageName, null))
94            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
95        runCatching { getApplication<Application>().startActivity(intent) }
96    }
97
98    fun uninstall(node: AppNode) {
99        @Suppress("DEPRECATION")
100        val intent = Intent(Intent.ACTION_DELETE)
101            .setData(Uri.fromParts("package", node.packageName, null))
102            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
103        runCatching { getApplication<Application>().startActivity(intent) }
104    }
105
106    override fun onCleared() {
107        runCatching { getApplication<Application>().unregisterReceiver(pkgReceiver) }
108    }
109}