Skip to content

Commit e9c9c46

Browse files
committed
add linux blocking
1 parent ff1d769 commit e9c9c46

12 files changed

Lines changed: 377 additions & 31 deletions

File tree

appintro/build.gradle.kts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ plugins {
99
alias(libs.plugins.composeHotReload)
1010
}
1111

12-
1312
kotlin {
1413
androidTarget {
1514
compilerOptions {
@@ -44,8 +43,6 @@ kotlin {
4443
androidMain.dependencies {
4544
implementation(compose.preview)
4645
implementation(libs.androidx.activity.compose)
47-
}
48-
commonMain.dependencies {
4946
implementation(libs.androidx.core.ktx)
5047
implementation(libs.material)
5148

composeApp/build.gradle.kts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,20 @@ kotlin {
7676
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
7777

7878
}
79-
commonTest.dependencies {
80-
implementation(libs.kotlin.test)
81-
}
79+
8280
iosMain.dependencies {
8381
implementation("io.ktor:ktor-client-darwin:3.0.1")
8482
}
8583
jvmMain.dependencies {
8684
implementation(compose.desktop.currentOs)
8785
implementation(libs.kotlinx.coroutinesSwing)
88-
86+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")
8987
implementation(libs.ktor.client.java)
9088
implementation("io.ktor:ktor-client-okhttp:3.0.1")
89+
9190
}
9291
}
9392
}
94-
9593
android {
9694
namespace = "neth.iecal.trease"
9795
compileSdk = libs.versions.android.compileSdk.get().toInt()
@@ -129,9 +127,14 @@ compose.desktop {
129127
mainClass = "neth.iecal.trease.MainKt"
130128

131129
nativeDistributions {
132-
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
130+
includeAllModules = false
131+
modules("java.instrument", "java.management", "jdk.unsupported")
132+
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb,TargetFormat.AppImage)
133133
packageName = "neth.iecal.trease"
134134
packageVersion = "1.0.0"
135+
buildTypes.release.proguard {
136+
isEnabled.set(false)
137+
}
135138
}
136139
}
137140
}

composeApp/proguard-rules.pro

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-dontwarn java.awt.**
2+
-dontwarn javax.annotation.**
3+
-dontwarn sun.misc.Unsafe
4+
-dontwarn kotlinx.coroutines.**
5+
6+
# OkHttp checks for Android classes at runtime. They don't exist on desktop.
7+
-dontwarn okhttp3.internal.platform.**
8+
-dontwarn android.net.**
9+
-dontwarn android.os.**
10+
-dontwarn android.security.**
11+
-dontwarn android.util.**
12+
-dontwarn android.net.http.**
13+
14+
# These libraries are optional dependencies for OkHttp
15+
-dontwarn org.conscrypt.**
16+
-dontwarn org.bouncycastle.**
17+
-dontwarn org.openjsse.**
18+
19+
# Suppress the hundreds of "duplicate class" notes about JNA
20+
-dontnote com.sun.jna.**

composeApp/src/androidMain/kotlin/neth/iecal/trease/ui/dialogs/AppSelectionDialog.kt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,24 @@ fun AppSelectionDialog(
7777

7878
val appResult = reloadApps(context.packageManager,context)
7979
if(appResult.isSuccess) {
80-
installedApps = appResult.getOrNull()!!
80+
installedApps = appResult.getOrDefault(emptyList())
8181
}
8282
installedApps = installedApps.sortedByDescending { currentSelection.contains (it.packageName ) }
8383

8484
val pm = context.packageManager
8585

8686
val tempMap = mutableMapOf<String, Drawable>()
8787
for (info in installedApps) {
88-
val icon = try { pm.getApplicationIcon(info.packageName) } catch (e: Exception) { pm.defaultActivityIcon }
89-
tempMap[info.packageName] = icon
88+
try {
89+
val icon = try {
90+
pm.getApplicationIcon(info.packageName)
91+
} catch (e: Exception) {
92+
pm.defaultActivityIcon
93+
}
94+
tempMap[info.packageName] = icon
95+
}catch (e:Exception){
96+
97+
}
9098
}
9199
icons = tempMap
92100
}
@@ -103,7 +111,6 @@ fun AppSelectionDialog(
103111
Column(
104112
modifier = Modifier.fillMaxWidth()
105113
) {
106-
// Search Bar
107114
OutlinedTextField(
108115
value = searchQuery,
109116
onValueChange = { searchQuery = it },
@@ -121,10 +128,15 @@ fun AppSelectionDialog(
121128
.fillMaxWidth()
122129
.padding(bottom = 16.dp)
123130
)
131+
Text(
132+
text = "Note that all the selected apps will be immediately killed. Please save all unsaved files before starting the app blocker.",
133+
style = MaterialTheme.typography.bodyMedium,
134+
color = MaterialTheme.colorScheme.error,
135+
modifier = Modifier.padding(vertical = 8.dp, horizontal = 4.dp)
136+
)
124137

125-
// App List
126138
LazyColumn(
127-
modifier = Modifier.heightIn(max = 400.dp) // Prevent dialog from taking full screen
139+
modifier = Modifier.heightIn(max = 400.dp)
128140
) {
129141
items(filteredApps, key = { it.packageName }) { app ->
130142
val isSelected = currentSelection.contains(app.packageName)

composeApp/src/commonMain/kotlin/neth/iecal/trease/api/TreeRepository.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package neth.iecal.trease.api
22

3-
43
import io.ktor.client.*
54
import io.ktor.client.call.*
65
import io.ktor.client.plugins.contentnegotiation.*

composeApp/src/commonMain/kotlin/neth/iecal/trease/platform/AppSelector.kt

Lines changed: 0 additions & 2 deletions
This file was deleted.

composeApp/src/jsMain/kotlin/neth/iecal/trease/ui/screens/HomeScreenJs.kt

Lines changed: 0 additions & 9 deletions
This file was deleted.

composeApp/src/jvmMain/kotlin/neth/iecal/trease/Platform.jvm.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import io.github.kdroidfilter.composemediaplayer.VideoPlayerState
88
import io.github.kdroidfilter.composemediaplayer.VideoPlayerSurface
99
import io.ktor.client.HttpClient
1010
import io.ktor.client.engine.okhttp.OkHttp
11+
import neth.iecal.trease.ui.LinuxAppSelectionDialog
12+
import neth.iecal.trease.utils.LinuxAppBlocker
1113
import neth.iecal.trease.viewmodels.HomeScreenViewModel
1214
import okio.FileSystem
1315
import java.io.File
@@ -43,17 +45,20 @@ actual fun PlatformVideoPlayer(
4345
)
4446
}
4547

48+
4649
@Composable
4750
actual fun FocusStarterDialog(
4851
viewModel: HomeScreenViewModel,
4952
onConfirm: () -> Unit,
5053
onDismissed: () -> Unit
5154
) {
52-
// Todo: Add blocking to jvm
53-
LaunchedEffect(Unit){
54-
onConfirm()
55-
}
55+
LinuxAppSelectionDialog(
56+
viewModel = viewModel,
57+
onConfirm = onConfirm,
58+
onDismiss = onDismissed
59+
)
5660
}
5761

5862
actual fun onForceStopFocus() {
63+
LinuxAppBlocker.getInstance().stopBlocking()
5964
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package neth.iecal.trease.models
2+
3+
data class AppInfo(
4+
val name: String,
5+
val packageName: String // In Linux, this is the executable name
6+
)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package neth.iecal.trease.ui
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.foundation.lazy.LazyColumn
6+
import androidx.compose.foundation.lazy.items
7+
import androidx.compose.material.icons.Icons
8+
import androidx.compose.material.icons.filled.Close
9+
import androidx.compose.material.icons.filled.Search
10+
import androidx.compose.material3.*
11+
import androidx.compose.runtime.*
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.draw.clip
15+
import androidx.compose.ui.text.style.TextOverflow
16+
import androidx.compose.ui.unit.dp
17+
import kotlinx.coroutines.Dispatchers
18+
import kotlinx.coroutines.launch
19+
import kotlinx.coroutines.withContext
20+
import neth.iecal.trease.models.AppInfo
21+
import neth.iecal.trease.utils.LinuxAppBlocker
22+
import neth.iecal.trease.viewmodels.HomeScreenViewModel
23+
24+
@Composable
25+
fun LinuxAppSelectionDialog(
26+
viewModel: HomeScreenViewModel,
27+
onConfirm: () -> Unit,
28+
onDismiss: () -> Unit
29+
) {
30+
var currentSelection by remember { mutableStateOf(emptySet<String>()) }
31+
var installedApps by remember { mutableStateOf(listOf<AppInfo>()) }
32+
var searchQuery by remember { mutableStateOf("") }
33+
var isLoading by remember { mutableStateOf(true) }
34+
35+
val filteredApps by remember(searchQuery, installedApps) {
36+
derivedStateOf {
37+
if (searchQuery.isBlank()) {
38+
installedApps
39+
} else {
40+
installedApps.filter {
41+
it.name.contains(searchQuery, ignoreCase = true) ||
42+
it.packageName.contains(searchQuery, ignoreCase = true)
43+
}
44+
}
45+
}
46+
}
47+
48+
val coroutineScope = rememberCoroutineScope()
49+
50+
LaunchedEffect(Unit) {
51+
withContext(Dispatchers.IO) {
52+
installedApps = LinuxAppBlocker.getInstance().getInstalledApps()
53+
isLoading = false
54+
}
55+
}
56+
57+
AlertDialog(
58+
onDismissRequest = onDismiss,
59+
title = {
60+
Text(
61+
text = "Select Apps to Block",
62+
style = MaterialTheme.typography.headlineSmall
63+
)
64+
},
65+
text = {
66+
Column(
67+
modifier = Modifier.fillMaxWidth()
68+
) {
69+
// Search Bar
70+
OutlinedTextField(
71+
value = searchQuery,
72+
onValueChange = { searchQuery = it },
73+
placeholder = { Text("Search apps...") },
74+
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
75+
trailingIcon = if (searchQuery.isNotEmpty()) {
76+
{
77+
IconButton(onClick = { searchQuery = "" }) {
78+
Icon(Icons.Default.Close, contentDescription = "Clear")
79+
}
80+
}
81+
} else null,
82+
singleLine = true,
83+
modifier = Modifier
84+
.fillMaxWidth()
85+
.padding(bottom = 16.dp)
86+
)
87+
88+
// App List
89+
if (isLoading) {
90+
Box(
91+
modifier = Modifier
92+
.fillMaxWidth()
93+
.heightIn(min = 200.dp),
94+
contentAlignment = Alignment.Center
95+
) {
96+
CircularProgressIndicator()
97+
}
98+
} else {
99+
LazyColumn(
100+
modifier = Modifier.heightIn(max = 400.dp)
101+
) {
102+
items(filteredApps, key = { it.packageName }) { app ->
103+
val isSelected = currentSelection.contains(app.packageName)
104+
105+
Row(
106+
verticalAlignment = Alignment.CenterVertically,
107+
modifier = Modifier
108+
.fillMaxWidth()
109+
.clip(MaterialTheme.shapes.small)
110+
.clickable {
111+
currentSelection = if (isSelected) {
112+
currentSelection - app.packageName
113+
} else {
114+
currentSelection + app.packageName
115+
}
116+
}
117+
.padding(vertical = 8.dp, horizontal = 4.dp)
118+
) {
119+
Checkbox(
120+
checked = isSelected,
121+
onCheckedChange = null
122+
)
123+
Spacer(modifier = Modifier.width(16.dp))
124+
Column {
125+
Text(
126+
text = app.name,
127+
style = MaterialTheme.typography.bodyLarge,
128+
maxLines = 1,
129+
overflow = TextOverflow.Ellipsis
130+
)
131+
Text(
132+
text = app.packageName,
133+
style = MaterialTheme.typography.bodySmall,
134+
color = MaterialTheme.colorScheme.onSurfaceVariant,
135+
maxLines = 1,
136+
overflow = TextOverflow.Ellipsis
137+
)
138+
}
139+
}
140+
}
141+
142+
if (filteredApps.isEmpty() && !isLoading) {
143+
item {
144+
Text(
145+
text = "No apps found",
146+
style = MaterialTheme.typography.bodyMedium,
147+
color = MaterialTheme.colorScheme.onSurfaceVariant,
148+
modifier = Modifier.padding(16.dp)
149+
)
150+
}
151+
}
152+
}
153+
}
154+
}
155+
},
156+
confirmButton = {
157+
Button(
158+
onClick = {
159+
if (currentSelection.isNotEmpty()) {
160+
coroutineScope.launch {
161+
val blocker = LinuxAppBlocker.getInstance()
162+
val durationMillis = viewModel.selectedMinutes.value * 60000L
163+
blocker.startBlocking(currentSelection, durationMillis)
164+
}
165+
onDismiss()
166+
onConfirm()
167+
}
168+
},
169+
enabled = currentSelection.isNotEmpty() && !isLoading
170+
) {
171+
Text("Start")
172+
}
173+
},
174+
dismissButton = {
175+
TextButton(onClick = onDismiss) {
176+
Text("Cancel")
177+
}
178+
}
179+
)
180+
}

0 commit comments

Comments
 (0)