Skip to content

Commit

Permalink
fix: update channel URL to match channel selection
Browse files Browse the repository at this point in the history
  • Loading branch information
andrekir committed Nov 25, 2024
1 parent 65d832e commit 4e9055c
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 91 deletions.
8 changes: 1 addition & 7 deletions app/src/main/java/com/geeksville/mesh/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import com.geeksville.mesh.databinding.ActivityMainBinding
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.toChannelSet
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.service.MeshServiceNotifications
import com.geeksville.mesh.service.ServiceRepository
Expand Down Expand Up @@ -303,12 +302,7 @@ class MainActivity : AppCompatActivity(), Logging {
when (appLinkAction) {
Intent.ACTION_VIEW -> {
debug("Asked to open a channel URL - ask user if they want to switch to that channel. If so send the config to the radio")
try {
appLinkData?.let { model.requestChannelSet(it.toChannelSet()) }
} catch (ex: Throwable) {
errormsg("Channel url error: ${ex.message}")
showSnackbar("${getString(R.string.channel_invalid)}: ${ex.message}")
}
appLinkData?.let(model::requestChannelUrl)

// We now wait for the device to connect, once connected, we ask the user if they want to switch to the new channel
}
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/java/com/geeksville/mesh/model/UIState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,11 @@ class UIViewModel @Inject constructor(
private val _requestChannelSet = MutableStateFlow<AppOnlyProtos.ChannelSet?>(null)
val requestChannelSet: StateFlow<AppOnlyProtos.ChannelSet?> get() = _requestChannelSet

fun requestChannelSet(channelSet: AppOnlyProtos.ChannelSet) {
_requestChannelSet.value = channelSet
fun requestChannelUrl(url: Uri) = runCatching {
_requestChannelSet.value = url.toChannelSet()
}.onFailure { ex ->
errormsg("Channel url error: ${ex.message}")
showSnackbar(R.string.channel_invalid)
}

/**
Expand Down
183 changes: 101 additions & 82 deletions app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ fun ChannelScreen(
) {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val clipboardManager = LocalClipboardManager.current

val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
val enabled = connectionState == MeshService.ConnectionState.CONNECTED && !viewModel.isManaged
Expand All @@ -138,22 +137,21 @@ fun ChannelScreen(
/* Holds selections made by the user for QR generation. */
val channelSelections = rememberSaveable(
saver = listSaver(
save = { stateList -> stateList.toList() },
save = { it.toList() },
restore = { it.toMutableStateList() }
)
) { mutableStateListOf(elements = Array(size = 8, init = { true })) }

val channelUrl = channelSet.getChannelUrl()
val selectedChannelSet = channelSet.copy {
val result = settings.filterIndexed { i, _ -> channelSelections.getOrNull(i) == true }
settings.clear()
settings.addAll(result)
}
val modemPresetName = Channel(loraConfig = channelSet.loraConfig).name

val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
try {
viewModel.requestChannelSet(Uri.parse(result.contents).toChannelSet())
} catch (ex: Throwable) {
errormsg("Channel url error: ${ex.message}")
viewModel.showSnackbar(R.string.channel_invalid)
}
viewModel.requestChannelUrl(Uri.parse(result.contents))
}
}

Expand Down Expand Up @@ -340,64 +338,10 @@ fun ChannelScreen(
}

item {
var valueState by remember(channelUrl) { mutableStateOf(channelUrl) }
val isError = valueState != channelUrl

OutlinedTextField(
value = valueState.toString(),
onValueChange = {
try {
valueState = Uri.parse(it)
channelSet = valueState.toChannelSet()
} catch (ex: Throwable) {
// channelSet failed to update, isError true
}
},
modifier = Modifier.fillMaxWidth(),
EditChannelUrl(
enabled = enabled,
label = { Text("URL") },
isError = isError,
trailingIcon = {
val isUrlEqual = channelUrl == channels.getChannelUrl()
IconButton(onClick = {
when {
isError -> valueState = channelUrl
!isUrlEqual -> viewModel.requestChannelSet(channels)
else -> {
// track how many times users share channels
GeeksvilleApplication.analytics.track(
"share",
DataPair("content_type", "channel")
)
clipboardManager.setText(AnnotatedString(channelUrl.toString()))
}
}
}) {
Icon(
imageVector = when {
isError -> Icons.TwoTone.Close
!isUrlEqual -> Icons.TwoTone.Check
else -> Icons.TwoTone.ContentCopy
},
contentDescription = when {
isError -> "Error"
!isUrlEqual -> stringResource(R.string.send)
else -> "Copy"
},
tint = if (isError) {
MaterialTheme.colors.error
} else {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
)
}
},
maxLines = 1,
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
channelUrl = selectedChannelSet.getChannelUrl(),
onConfirm = viewModel::requestChannelUrl
)
}

Expand All @@ -413,21 +357,20 @@ fun ChannelScreen(
})
}

if (isEditing) item {
PreferenceFooter(
enabled = enabled,
onCancelClicked = {
focusManager.clearFocus()
showChannelEditor = false
channelSet = channels
},
onSaveClicked = {
focusManager.clearFocus()
// viewModel.setRequestChannelUrl(channelUrl)
sendButton()
})
} else {
item {
item {
if (isEditing) {
PreferenceFooter(
enabled = enabled,
onCancelClicked = {
focusManager.clearFocus()
showChannelEditor = false
channelSet = channels
},
onSaveClicked = {
focusManager.clearFocus()
sendButton()
})
} else {
PreferenceFooter(
enabled = enabled,
negativeText = R.string.reset,
Expand All @@ -438,14 +381,90 @@ fun ChannelScreen(
positiveText = R.string.scan,
onPositiveClicked = {
focusManager.clearFocus()
// viewModel.setRequestChannelUrl(channelUrl)
if (context.hasCameraPermission()) zxingScan() else requestPermissionAndScan()
})
}
}
}
}

@Suppress("LongMethod")
@Composable
private fun EditChannelUrl(
enabled: Boolean,
channelUrl: Uri,
modifier: Modifier = Modifier,
onConfirm: (Uri) -> Unit
) {
val focusManager = LocalFocusManager.current
val clipboardManager = LocalClipboardManager.current

var valueState by remember(channelUrl) { mutableStateOf(channelUrl) }
var isError by remember { mutableStateOf(false) }

OutlinedTextField(
value = valueState.toString(),
onValueChange = {
isError = runCatching {
valueState = Uri.parse(it)
valueState.toChannelSet()
}.isFailure
},
modifier = modifier.fillMaxWidth(),
enabled = enabled,
label = { Text("URL") },
isError = isError,
trailingIcon = {
val isUrlEqual = valueState == channelUrl
IconButton(onClick = {
when {
isError -> {
isError = false
valueState = channelUrl
}

!isUrlEqual -> {
onConfirm(valueState)
valueState = channelUrl
}

else -> {
// track how many times users share channels
GeeksvilleApplication.analytics.track(
"share", DataPair("content_type", "channel")
)
clipboardManager.setText(AnnotatedString(valueState.toString()))
}
}
}) {
Icon(
imageVector = when {
isError -> Icons.TwoTone.Close
!isUrlEqual -> Icons.TwoTone.Check
else -> Icons.TwoTone.ContentCopy
},
contentDescription = when {
isError -> stringResource(R.string.share)
!isUrlEqual -> stringResource(R.string.send)
else -> stringResource(R.string.share)
},
tint = if (isError) {
MaterialTheme.colors.error
} else {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
)
}
},
maxLines = 1,
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
)
}

@Composable
private fun QrCodeImage(
enabled: Boolean,
Expand Down

0 comments on commit 4e9055c

Please sign in to comment.