Skip to content

Commit 7709261

Browse files
wzrayyysupershadoe
andcommitted
feat: show presets on the main screen
Closes #127 Co-authored-by: supershadoe <shadoe@shadoe.dev>
1 parent 320d2c2 commit 7709261

6 files changed

Lines changed: 181 additions & 0 deletions

File tree

app/src/main/kotlin/dev/shadoe/delta/control/ControlScreen.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ import dev.shadoe.delta.control.components.BlocklistComponentActions
2424
import dev.shadoe.delta.control.components.BlocklistComponentState
2525
import dev.shadoe.delta.control.components.ConnectedClientsListActions
2626
import dev.shadoe.delta.control.components.ConnectedClientsListState
27+
import dev.shadoe.delta.control.components.PresetConfirmDialog
2728
import dev.shadoe.delta.control.components.SoftApControl
2829
import dev.shadoe.delta.control.components.SoftApControlViewModel
2930
import dev.shadoe.delta.control.components.blocklistComponent
3031
import dev.shadoe.delta.control.components.connectedClientsList
32+
import dev.shadoe.delta.control.components.presetsComponent
33+
import dev.shadoe.delta.data.database.models.Preset
3134

3235
@Composable
3336
fun ControlScreen(
@@ -36,6 +39,8 @@ fun ControlScreen(
3639
vm: ControlViewModel = viewModel(),
3740
) {
3841
var isBlocklistShown by remember { mutableStateOf(false) }
42+
var isPresetsListShown by remember { mutableStateOf(false) }
43+
var presetToApply by remember { mutableStateOf<Preset?>(null) }
3944
var devicesToBlock by
4045
remember(isBlocklistShown) { mutableStateOf(setOf<TetheredClient>()) }
4146
var devicesToUnblock by
@@ -46,6 +51,7 @@ fun ControlScreen(
4651
val tetheredClients by vm.connectedClients.collectAsState(listOf())
4752
val blockedClients by vm.blockedClients.collectAsState(listOf())
4853
val supportsBlocklist by vm.supportsBlocklist.collectAsState(false)
54+
val presets by vm.presets.collectAsState(listOf())
4955

5056
val featureNotSupportedSnackbar =
5157
object : SnackbarVisuals {
@@ -68,6 +74,13 @@ fun ControlScreen(
6874
override val withDismissAction = true
6975
override val actionLabel = null
7076
}
77+
val presetAppliedSnackbar =
78+
object : SnackbarVisuals {
79+
override val message = stringResource(R.string.preset_applied)
80+
override val duration = SnackbarDuration.Short
81+
override val withDismissAction = true
82+
override val actionLabel = null
83+
}
7184

7285
LazyColumn(modifier = modifier) {
7386
item {
@@ -103,6 +116,12 @@ fun ControlScreen(
103116
),
104117
)
105118
}
119+
presetsComponent(
120+
isPresetsShown = isPresetsListShown,
121+
presets = presets,
122+
applyPreset = { presetToApply = it },
123+
onPresetsListToggled = { isPresetsListShown = !isPresetsListShown },
124+
)
106125
if (supportsBlocklist) {
107126
blocklistComponent(
108127
state =
@@ -125,4 +144,16 @@ fun ControlScreen(
125144
)
126145
}
127146
}
147+
148+
presetToApply?.let { it1 ->
149+
PresetConfirmDialog(
150+
preset = it1,
151+
onApply = {
152+
vm.applyPreset(it)
153+
presetToApply = null
154+
onShowSnackbar(presetAppliedSnackbar)
155+
},
156+
onDismiss = { presetToApply = null },
157+
)
158+
}
128159
}

app/src/main/kotlin/dev/shadoe/delta/control/ControlViewModel.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package dev.shadoe.delta.control
33
import androidx.lifecycle.ViewModel
44
import dagger.hilt.android.lifecycle.HiltViewModel
55
import dev.shadoe.delta.api.ACLDevice
6+
import dev.shadoe.delta.api.SoftApConfiguration
7+
import dev.shadoe.delta.data.database.dao.PresetDao
8+
import dev.shadoe.delta.data.database.models.Preset
69
import dev.shadoe.delta.data.softap.SoftApBlocklistManager
10+
import dev.shadoe.delta.data.softap.SoftApController
711
import dev.shadoe.delta.data.softap.SoftApStateStore
812
import javax.inject.Inject
913
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -14,7 +18,9 @@ class ControlViewModel
1418
@Inject
1519
constructor(
1620
state: SoftApStateStore,
21+
presetDao: PresetDao,
1722
private val softApBlocklistManager: SoftApBlocklistManager,
23+
private val softApController: SoftApController,
1824
) : ViewModel() {
1925
@OptIn(ExperimentalCoroutinesApi::class)
2026
val enabledState = state.status.mapLatest { it.enabledState }
@@ -26,11 +32,32 @@ constructor(
2632
val supportsBlocklist =
2733
state.status.mapLatest { it.capabilities.clientForceDisconnectSupported }
2834

35+
val presets = presetDao.observePresets()
36+
2937
val blockedClients = softApBlocklistManager.blockedClients
3038

3139
fun blockDevices(devices: Iterable<ACLDevice>) =
3240
softApBlocklistManager.blockDevices(devices)
3341

3442
fun unblockDevices(devices: Iterable<ACLDevice>) =
3543
softApBlocklistManager.unblockDevices(devices)
44+
45+
fun applyPreset(preset: Preset) =
46+
softApController.updateSoftApConfiguration(
47+
preset.run {
48+
SoftApConfiguration(
49+
ssid = ssid,
50+
passphrase = passphrase,
51+
securityType = securityType,
52+
macRandomizationSetting = macRandomizationSetting,
53+
isHidden = isHidden,
54+
speedType = speedType,
55+
blockedDevices = blockedDevices,
56+
allowedClients = allowedClients,
57+
isAutoShutdownEnabled = isAutoShutdownEnabled,
58+
autoShutdownTimeout = autoShutdownTimeout,
59+
maxClientLimit = maxClientLimit,
60+
)
61+
}
62+
)
3663
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.shadoe.delta.control.components
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.material.icons.Icons
7+
import androidx.compose.material.icons.automirrored.rounded.Input
8+
import androidx.compose.material3.Icon
9+
import androidx.compose.material3.IconButton
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.res.stringResource
15+
import androidx.compose.ui.unit.dp
16+
import dev.shadoe.delta.R
17+
18+
@Composable
19+
fun PresetComponent(
20+
presetName: String,
21+
applyPreset: () -> Unit,
22+
modifier: Modifier = Modifier,
23+
) {
24+
Row(
25+
modifier =
26+
modifier.then(Modifier.padding(horizontal = 16.dp, vertical = 4.dp)),
27+
verticalAlignment = Alignment.CenterVertically,
28+
) {
29+
Column(modifier = Modifier.weight(1f)) { Text(text = presetName) }
30+
Row {
31+
IconButton(onClick = applyPreset) {
32+
Icon(
33+
imageVector = Icons.AutoMirrored.Rounded.Input,
34+
contentDescription = stringResource(R.string.preset_apply_button),
35+
)
36+
}
37+
}
38+
}
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dev.shadoe.delta.control.components
2+
3+
import androidx.compose.material3.AlertDialog
4+
import androidx.compose.material3.Text
5+
import androidx.compose.material3.TextButton
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.res.stringResource
8+
import dev.shadoe.delta.R
9+
import dev.shadoe.delta.data.database.models.Preset
10+
11+
@Composable
12+
fun PresetConfirmDialog(
13+
preset: Preset,
14+
onApply: (Preset) -> Unit,
15+
onDismiss: () -> Unit,
16+
) {
17+
AlertDialog(
18+
onDismissRequest = onDismiss,
19+
confirmButton = {
20+
TextButton(onClick = { onApply(preset) }) {
21+
Text(stringResource(R.string.preset_apply_button))
22+
}
23+
},
24+
dismissButton = {
25+
TextButton(onClick = onDismiss) {
26+
Text(stringResource(R.string.cancel_button))
27+
}
28+
},
29+
title = {
30+
Text(stringResource(R.string.preset_confirm_title, preset.presetName))
31+
},
32+
text = { Text(stringResource(R.string.preset_confirm_body)) },
33+
)
34+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dev.shadoe.delta.control.components
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.foundation.lazy.LazyListScope
7+
import androidx.compose.foundation.lazy.items
8+
import androidx.compose.material3.Text
9+
import androidx.compose.ui.Alignment
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.res.stringResource
12+
import androidx.compose.ui.unit.dp
13+
import dev.shadoe.delta.R
14+
import dev.shadoe.delta.common.components.FadeInExpanded
15+
import dev.shadoe.delta.common.components.FoldableWrapper
16+
import dev.shadoe.delta.data.database.models.Preset
17+
18+
internal fun LazyListScope.presetsComponent(
19+
isPresetsShown: Boolean,
20+
presets: List<Preset>,
21+
applyPreset: (Preset) -> Unit,
22+
onPresetsListToggled: () -> Unit,
23+
) {
24+
item {
25+
FoldableWrapper(
26+
text = stringResource(R.string.presets_setting),
27+
foldableState = isPresetsShown,
28+
onFoldableToggled = onPresetsListToggled,
29+
modifier = Modifier.padding(horizontal = 16.dp),
30+
)
31+
}
32+
item {
33+
FadeInExpanded(visible = isPresetsShown && presets.isEmpty()) {
34+
Box(
35+
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
36+
contentAlignment = Alignment.Center,
37+
) {
38+
Text(text = stringResource(R.string.presets_none_saved))
39+
}
40+
}
41+
}
42+
items(presets) {
43+
FadeInExpanded(visible = isPresetsShown) {
44+
PresetComponent(it.presetName, applyPreset = { applyPreset(it) })
45+
}
46+
}
47+
}

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,7 @@
215215
<string name="shizuku_setup_not_running">Not running.</string>
216216
<string name="shizuku_setup_not_connected">Not connected.</string>
217217
<string name="shizuku_setup_connected">Connected ✅</string>
218+
<string name="preset_applied">Preset applied.</string>
219+
<string name="preset_confirm_title">Apply %s</string>
220+
<string name="preset_confirm_body">Are you sure you want to apply this preset? Hotspot will be restarted if running.</string>
218221
</resources>

0 commit comments

Comments
 (0)