Skip to content

Commit 36aa2ba

Browse files
committed
Further improvements
1 parent 7933327 commit 36aa2ba

File tree

13 files changed

+160
-67
lines changed

13 files changed

+160
-67
lines changed

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal fun CheckboxCell(
3333
checked: Boolean,
3434
onCheckedChange: (Boolean) -> Unit,
3535
background: Color = MaterialTheme.colorScheme.secondaryContainer,
36-
startPadding: Dp = Dimens.cellStartPadding,
36+
startPadding: Dp = Dimens.mediumPadding,
3737
endPadding: Dp = Dimens.cellEndPadding,
3838
minHeight: Dp = Dimens.cellHeight
3939
) {

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DropdownMenuCell.kt

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.mullvad.mullvadvpn.compose.cell
22

33
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
45
import androidx.compose.material3.DropdownMenu
56
import androidx.compose.material3.DropdownMenuItem
67
import androidx.compose.material3.Icon
@@ -14,9 +15,12 @@ import androidx.compose.runtime.remember
1415
import androidx.compose.runtime.setValue
1516
import androidx.compose.ui.Modifier
1617
import androidx.compose.ui.graphics.Color
18+
import androidx.compose.ui.layout.layout
1719
import androidx.compose.ui.res.painterResource
1820
import androidx.compose.ui.text.TextStyle
1921
import androidx.compose.ui.tooling.preview.Preview
22+
import androidx.compose.ui.unit.Dp
23+
import androidx.compose.ui.unit.dp
2024
import net.mullvad.mullvadvpn.R
2125
import net.mullvad.mullvadvpn.lib.theme.AppTheme
2226
import net.mullvad.mullvadvpn.lib.theme.Dimens
@@ -44,7 +48,8 @@ fun DropdownMenuCell(
4448
textStyle: TextStyle = MaterialTheme.typography.titleMedium,
4549
textColor: Color = MaterialTheme.colorScheme.onPrimary,
4650
background: Color = MaterialTheme.colorScheme.primary,
47-
dropdownBackground: Color = MaterialTheme.colorScheme.background
51+
dropdownBackground: Color = MaterialTheme.colorScheme.background,
52+
dropdownBorderColor: Color = MaterialTheme.colorScheme.primary
4853
) {
4954
var showMenu by remember { mutableStateOf(false) }
5055
BaseCell(
@@ -68,7 +73,14 @@ fun DropdownMenuCell(
6873
DropdownMenu(
6974
expanded = true,
7075
onDismissRequest = { showMenu = false },
71-
modifier = Modifier.background(dropdownBackground)
76+
modifier =
77+
Modifier.background(dropdownBackground)
78+
.border(
79+
width = Dimens.dropdownMenuBorder,
80+
color = dropdownBorderColor,
81+
MaterialTheme.shapes.extraSmall
82+
)
83+
.crop(vertical = Dimens.dropdownMenuVerticalPadding)
7284
) {
7385
contextMenuItems { showMenu = false }
7486
}
@@ -79,3 +91,19 @@ fun DropdownMenuCell(
7991
endPadding = Dimens.smallPadding
8092
)
8193
}
94+
95+
fun Modifier.crop(
96+
horizontal: Dp = 0.dp,
97+
vertical: Dp = 0.dp,
98+
): Modifier =
99+
this.layout { measurable, constraints ->
100+
val placeable = measurable.measure(constraints)
101+
fun Dp.toPxInt(): Int = this.toPx().toInt()
102+
103+
layout(
104+
placeable.width - (horizontal * 2).toPxInt(),
105+
placeable.height - (vertical * 2).toPxInt()
106+
) {
107+
placeable.placeRelative(-horizontal.toPx().toInt(), -vertical.toPx().toInt())
108+
}
109+
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/RelayLocationCell.kt

+3
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ fun NormalRelayLocationCell(
256256
modifier: Modifier = Modifier,
257257
activeColor: Color = MaterialTheme.colorScheme.selected,
258258
inactiveColor: Color = MaterialTheme.colorScheme.error,
259+
disabledColor: Color = MaterialTheme.colorScheme.onSecondary,
259260
selectedItem: RelayItem? = null,
260261
onSelectRelay: (item: RelayItem) -> Unit = {}
261262
) {
@@ -271,6 +272,8 @@ fun NormalRelayLocationCell(
271272
color =
272273
when {
273274
selected -> Color.Transparent
275+
relayItem is RelayItem.CustomList && !relayItem.hasChildren ->
276+
disabledColor
274277
relayItem.active -> activeColor
275278
else -> inactiveColor
276279
},
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,21 @@
11
package net.mullvad.mullvadvpn.compose.component
22

3-
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.layout.Box
5-
import androidx.compose.foundation.layout.fillMaxSize
6-
import androidx.compose.foundation.layout.size
73
import androidx.compose.material3.Checkbox
84
import androidx.compose.material3.CheckboxDefaults
95
import androidx.compose.material3.MaterialTheme
106
import androidx.compose.runtime.Composable
11-
import androidx.compose.ui.Modifier
12-
import androidx.compose.ui.graphics.Color
13-
import net.mullvad.mullvadvpn.lib.theme.Dimens
147
import net.mullvad.mullvadvpn.lib.theme.color.selected
158

169
@Composable
1710
fun MullvadCheckbox(checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
18-
Box(
19-
modifier =
20-
Modifier.size(Dimens.checkBoxSize).background(Color.White, MaterialTheme.shapes.small)
21-
) {
22-
Checkbox(
23-
modifier = Modifier.fillMaxSize(),
24-
checked = checked,
25-
onCheckedChange = onCheckedChange,
26-
colors =
27-
CheckboxDefaults.colors(
28-
checkedColor = Color.Transparent,
29-
uncheckedColor = Color.Transparent,
30-
checkmarkColor = MaterialTheme.colorScheme.selected
31-
),
32-
)
33-
}
11+
Checkbox(
12+
checked = checked,
13+
onCheckedChange = onCheckedChange,
14+
colors =
15+
CheckboxDefaults.colors(
16+
checkedColor = MaterialTheme.colorScheme.onPrimary,
17+
uncheckedColor = MaterialTheme.colorScheme.onPrimary,
18+
checkmarkColor = MaterialTheme.colorScheme.selected
19+
)
20+
)
3421
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialog.kt

+17-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import androidx.compose.runtime.LaunchedEffect
99
import androidx.compose.runtime.collectAsState
1010
import androidx.compose.runtime.mutableStateOf
1111
import androidx.compose.runtime.remember
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.focus.FocusRequester
14+
import androidx.compose.ui.focus.focusRequester
15+
import androidx.compose.ui.focus.onFocusChanged
16+
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
1217
import androidx.compose.ui.res.stringResource
1318
import androidx.compose.ui.text.input.KeyboardType
1419
import androidx.compose.ui.tooling.preview.Preview
@@ -70,7 +75,10 @@ fun CreateCustomListDialog(
7075
onInputChanged: () -> Unit = {},
7176
onDismiss: () -> Unit = {}
7277
) {
78+
val focusRequester = remember { FocusRequester() }
79+
val keyboardController = LocalSoftwareKeyboardController.current
7380
val name = remember { mutableStateOf("") }
81+
7482
AlertDialog(
7583
title = {
7684
Text(
@@ -108,9 +116,17 @@ fun CreateCustomListDialog(
108116
style = MaterialTheme.typography.bodySmall
109117
)
110118
}
111-
}
119+
},
120+
modifier =
121+
Modifier.focusRequester(focusRequester).onFocusChanged { focusState ->
122+
if (focusState.hasFocus) {
123+
keyboardController?.show()
124+
}
125+
}
112126
)
113127
}
128+
129+
LaunchedEffect(Unit) { focusRequester.requestFocus() }
114130
},
115131
containerColor = MaterialTheme.colorScheme.background,
116132
titleContentColor = MaterialTheme.colorScheme.onBackground,

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialog.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ fun DeleteCustomListConfirmationDialog(navigator: ResultBackNavigator<Boolean>,
6262
)
6363
},
6464
dismissButton = {
65-
NegativeButton(
66-
onClick = { navigator.navigateBack(result = true) },
67-
text = stringResource(id = R.string.delete_list)
68-
)
69-
},
70-
confirmButton = {
7165
PrimaryButton(
7266
modifier = Modifier.focusRequester(FocusRequester()),
7367
onClick = { navigator.navigateBack(result = false) },
7468
text = stringResource(id = R.string.cancel)
7569
)
7670
},
71+
confirmButton = {
72+
NegativeButton(
73+
onClick = { navigator.navigateBack(result = true) },
74+
text = stringResource(id = R.string.delete_list)
75+
)
76+
},
7777
containerColor = MaterialTheme.colorScheme.background
7878
)
7979
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt

+22-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import androidx.compose.runtime.LaunchedEffect
99
import androidx.compose.runtime.collectAsState
1010
import androidx.compose.runtime.mutableStateOf
1111
import androidx.compose.runtime.remember
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.focus.FocusRequester
14+
import androidx.compose.ui.focus.focusRequester
15+
import androidx.compose.ui.focus.onFocusChanged
16+
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
1217
import androidx.compose.ui.res.stringResource
1318
import androidx.compose.ui.text.input.KeyboardType
1419
import androidx.compose.ui.tooling.preview.Preview
@@ -65,11 +70,13 @@ fun EditCustomListNameDialog(
6570
onInputChanged: () -> Unit = {},
6671
onDismiss: () -> Unit = {}
6772
) {
73+
val focusRequester = remember { FocusRequester() }
74+
val keyboardController = LocalSoftwareKeyboardController.current
6875
val input = remember { mutableStateOf(name) }
6976
AlertDialog(
7077
title = {
7178
Text(
72-
text = stringResource(id = R.string.create_new_list),
79+
text = stringResource(id = R.string.update_list_name),
7380
)
7481
},
7582
text = {
@@ -80,7 +87,11 @@ fun EditCustomListNameDialog(
8087
input.value = it
8188
onInputChanged()
8289
},
83-
onSubmit = updateName,
90+
onSubmit = {
91+
if (it.isNotEmpty()) {
92+
updateName(it)
93+
}
94+
},
8495
keyboardType = KeyboardType.Text,
8596
placeholderText = "",
8697
isValidValue = input.value.isNotBlank(),
@@ -103,9 +114,17 @@ fun EditCustomListNameDialog(
103114
style = MaterialTheme.typography.bodySmall
104115
)
105116
}
106-
}
117+
},
118+
modifier =
119+
Modifier.focusRequester(focusRequester).onFocusChanged { focusState ->
120+
if (focusState.hasFocus) {
121+
keyboardController?.show()
122+
}
123+
}
107124
)
108125
}
126+
127+
LaunchedEffect(Unit) { focusRequester.requestFocus() }
109128
},
110129
containerColor = MaterialTheme.colorScheme.background,
111130
titleContentColor = MaterialTheme.colorScheme.onBackground,

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreen.kt

+51-30
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package net.mullvad.mullvadvpn.compose.screen
22

33
import androidx.compose.animation.animateContentSize
44
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.layout.Column
56
import androidx.compose.foundation.layout.Row
67
import androidx.compose.foundation.layout.fillMaxSize
78
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.height
810
import androidx.compose.foundation.layout.padding
911
import androidx.compose.foundation.layout.systemBarsPadding
1012
import androidx.compose.foundation.lazy.LazyColumn
@@ -35,6 +37,7 @@ import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicator
3537
import net.mullvad.mullvadvpn.compose.constant.CommonContentKey
3638
import net.mullvad.mullvadvpn.compose.constant.ContentType
3739
import net.mullvad.mullvadvpn.compose.state.CustomListLocationsUiState
40+
import net.mullvad.mullvadvpn.compose.textfield.SearchTextField
3841
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
3942
import net.mullvad.mullvadvpn.lib.theme.AppTheme
4043
import net.mullvad.mullvadvpn.lib.theme.Dimens
@@ -69,6 +72,7 @@ fun CustomListLocations(navigator: DestinationsNavigator, customListKey: String,
6972
val state by customListsViewModel.uiState.collectAsState()
7073
CustomListLocationsScreen(
7174
uiState = state,
75+
onSearchTermInput = customListsViewModel::onSearchTermInput,
7276
onSaveClick = customListsViewModel::save,
7377
onRelaySelectionClick = customListsViewModel::onRelaySelectionClick,
7478
onBackClick = navigator::navigateUp
@@ -78,6 +82,7 @@ fun CustomListLocations(navigator: DestinationsNavigator, customListKey: String,
7882
@Composable
7983
fun CustomListLocationsScreen(
8084
uiState: CustomListLocationsUiState,
85+
onSearchTermInput: (String) -> Unit = {},
8186
onSaveClick: () -> Unit = {},
8287
onRelaySelectionClick: (RelayItem, selected: Boolean) -> Unit = { _, _ -> },
8388
onBackClick: () -> Unit = {}
@@ -89,12 +94,19 @@ fun CustomListLocationsScreen(
8994
topBar = {
9095
CustomListLocationsTopBar(
9196
uiState = uiState,
97+
onSearchTermInput = onSearchTermInput,
9298
onBackClick = onBackClick,
9399
onSaveClick = onSaveClick
94100
)
95101
},
96102
content = { contentPadding ->
97-
LazyColumn(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
103+
LazyColumn(
104+
horizontalAlignment = Alignment.CenterHorizontally,
105+
modifier =
106+
Modifier.padding(contentPadding)
107+
.padding(top = Dimens.verticalSpace)
108+
.fillMaxSize()
109+
) {
98110
when (uiState) {
99111
is CustomListLocationsUiState.Loading -> {
100112
loading()
@@ -114,40 +126,49 @@ fun CustomListLocationsScreen(
114126
@Composable
115127
private fun CustomListLocationsTopBar(
116128
uiState: CustomListLocationsUiState,
129+
onSearchTermInput: (String) -> Unit,
117130
onBackClick: () -> Unit,
118131
onSaveClick: () -> Unit
119132
) {
120-
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
121-
IconButton(onClick = onBackClick) {
122-
Icon(
123-
painter = painterResource(id = R.drawable.icon_back),
124-
contentDescription = null,
125-
tint = Color.Unspecified,
126-
)
127-
}
128-
Text(
129-
text =
130-
stringResource(
131-
if (uiState.newList) {
132-
R.string.add_locations
133-
} else {
134-
R.string.edit_locations
135-
}
136-
),
137-
modifier = Modifier.weight(1f).padding(end = Dimens.titleIconSize),
138-
textAlign = TextAlign.Start,
139-
style = MaterialTheme.typography.titleLarge,
140-
color = MaterialTheme.colorScheme.onPrimary
141-
)
142-
IconButton(onClick = {}) {
143-
Icon(
144-
painter = painterResource(id = R.drawable.icons_search),
145-
contentDescription = null,
146-
tint = Color.Unspecified,
133+
Column(modifier = Modifier.fillMaxWidth()) {
134+
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
135+
IconButton(onClick = onBackClick) {
136+
Icon(
137+
painter = painterResource(id = R.drawable.icon_back),
138+
contentDescription = null,
139+
tint = Color.Unspecified,
140+
)
141+
}
142+
Text(
143+
text =
144+
stringResource(
145+
if (uiState.newList) {
146+
R.string.add_locations
147+
} else {
148+
R.string.edit_locations
149+
}
150+
),
151+
modifier = Modifier.weight(1f).padding(end = Dimens.titleIconSize),
152+
textAlign = TextAlign.Start,
153+
style = MaterialTheme.typography.titleLarge,
154+
color = MaterialTheme.colorScheme.onPrimary
147155
)
156+
TextButton(onClick = onSaveClick) {
157+
Text(
158+
text = stringResource(R.string.save),
159+
color = MaterialTheme.colorScheme.onPrimary
160+
)
161+
}
148162
}
149-
TextButton(onClick = onSaveClick) {
150-
Text(text = stringResource(R.string.save), color = MaterialTheme.colorScheme.onPrimary)
163+
SearchTextField(
164+
modifier =
165+
Modifier.fillMaxWidth()
166+
.height(Dimens.searchFieldHeight)
167+
.padding(horizontal = Dimens.searchFieldHorizontalPadding),
168+
backgroundColor = MaterialTheme.colorScheme.tertiaryContainer,
169+
textColor = MaterialTheme.colorScheme.onTertiaryContainer,
170+
) { searchString ->
171+
onSearchTermInput.invoke(searchString)
151172
}
152173
}
153174
}

0 commit comments

Comments
 (0)