tawasuyu

sergio/humanoid

HTML 50Markdown 1TOML 10 Sutichaykuna

⎇ 3feee86e320e76ed9ddd32f09ed18d20260c4e2a launcher/src/main/java/com/rizoma/orbita/ui/OrbitaViewModel.kt 101 siq'ikuna · 3.5 KB Ñawpa willay ch'uya

Órbita: launcher orbital anti-rejilla (v0.1) 3feee86e3 · 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    ) { apps, counts, pinned ->
37        apps.map { node ->
38            node.copy(
39                score = (counts[node.key] ?: 0).toFloat(),
40                pinned = node.key in pinned,
41            )
42        }.sortedWith(
43            compareByDescending<AppNode> { it.pinned }
44                .thenByDescending { it.score }
45                .thenBy { it.label.lowercase() }
46        )
47    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
48
49    private val pkgReceiver = object : BroadcastReceiver() {
50        override fun onReceive(context: Context?, intent: Intent?) = refresh()
51    }
52
53    init {
54        refresh()
55        val filter = IntentFilter().apply {
56            addAction(Intent.ACTION_PACKAGE_ADDED)
57            addAction(Intent.ACTION_PACKAGE_REMOVED)
58            addAction(Intent.ACTION_PACKAGE_CHANGED)
59            addDataScheme("package")
60        }
61        getApplication<Application>().registerReceiver(pkgReceiver, filter)
62    }
63
64    fun refresh() {
65        viewModelScope.launch(Dispatchers.IO) {
66            rawApps.value = repo.loadApps()
67        }
68    }
69
70    fun launch(node: AppNode) {
71        val intent = Intent(Intent.ACTION_MAIN)
72            .addCategory(Intent.CATEGORY_LAUNCHER)
73            .setComponent(ComponentName(node.packageName, node.activityName))
74            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
75        runCatching { getApplication<Application>().startActivity(intent) }
76        viewModelScope.launch { usage.record(node.key) }
77    }
78
79    fun togglePin(node: AppNode) {
80        viewModelScope.launch { usage.setPinned(node.key, !node.pinned) }
81    }
82
83    fun openInfo(node: AppNode) {
84        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
85            .setData(Uri.fromParts("package", node.packageName, null))
86            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
87        runCatching { getApplication<Application>().startActivity(intent) }
88    }
89
90    fun uninstall(node: AppNode) {
91        @Suppress("DEPRECATION")
92        val intent = Intent(Intent.ACTION_DELETE)
93            .setData(Uri.fromParts("package", node.packageName, null))
94            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
95        runCatching { getApplication<Application>().startActivity(intent) }
96    }
97
98    override fun onCleared() {
99        runCatching { getApplication<Application>().unregisterReceiver(pkgReceiver) }
100    }
101}