android, desktop: connect remote desktop via multicast (#3442)
* android, desktop: connect remote desktop via multicast * changes * string --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
parent
f7903c5c83
commit
b2dbb558f9
@ -2921,7 +2921,7 @@ enum class NotificationPreviewMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class RemoteCtrlSession(
|
data class RemoteCtrlSession(
|
||||||
val ctrlAppInfo: CtrlAppInfo,
|
val ctrlAppInfo: CtrlAppInfo?,
|
||||||
val appVersion: String,
|
val appVersion: String,
|
||||||
val sessionState: UIRemoteCtrlSessionState
|
val sessionState: UIRemoteCtrlSessionState
|
||||||
) {
|
) {
|
||||||
@ -2939,6 +2939,7 @@ data class RemoteCtrlSession(
|
|||||||
@Serializable
|
@Serializable
|
||||||
sealed class RemoteCtrlSessionState {
|
sealed class RemoteCtrlSessionState {
|
||||||
@Serializable @SerialName("starting") object Starting: RemoteCtrlSessionState()
|
@Serializable @SerialName("starting") object Starting: RemoteCtrlSessionState()
|
||||||
|
@Serializable @SerialName("searching") object Searching: RemoteCtrlSessionState()
|
||||||
@Serializable @SerialName("connecting") object Connecting: RemoteCtrlSessionState()
|
@Serializable @SerialName("connecting") object Connecting: RemoteCtrlSessionState()
|
||||||
@Serializable @SerialName("pendingConfirmation") data class PendingConfirmation(val sessionCode: String): RemoteCtrlSessionState()
|
@Serializable @SerialName("pendingConfirmation") data class PendingConfirmation(val sessionCode: String): RemoteCtrlSessionState()
|
||||||
@Serializable @SerialName("connected") data class Connected(val sessionCode: String): RemoteCtrlSessionState()
|
@Serializable @SerialName("connected") data class Connected(val sessionCode: String): RemoteCtrlSessionState()
|
||||||
@ -2946,6 +2947,8 @@ sealed class RemoteCtrlSessionState {
|
|||||||
|
|
||||||
sealed class UIRemoteCtrlSessionState {
|
sealed class UIRemoteCtrlSessionState {
|
||||||
@Serializable @SerialName("starting") object Starting: UIRemoteCtrlSessionState()
|
@Serializable @SerialName("starting") object Starting: UIRemoteCtrlSessionState()
|
||||||
|
@Serializable @SerialName("searching") object Searching: UIRemoteCtrlSessionState()
|
||||||
|
@Serializable @SerialName("found") data class Found(val remoteCtrl: RemoteCtrlInfo, val compatible: Boolean): UIRemoteCtrlSessionState()
|
||||||
@Serializable @SerialName("connecting") data class Connecting(val remoteCtrl_: RemoteCtrlInfo? = null): UIRemoteCtrlSessionState()
|
@Serializable @SerialName("connecting") data class Connecting(val remoteCtrl_: RemoteCtrlInfo? = null): UIRemoteCtrlSessionState()
|
||||||
@Serializable @SerialName("pendingConfirmation") data class PendingConfirmation(val remoteCtrl_: RemoteCtrlInfo? = null, val sessionCode: String): UIRemoteCtrlSessionState()
|
@Serializable @SerialName("pendingConfirmation") data class PendingConfirmation(val remoteCtrl_: RemoteCtrlInfo? = null, val sessionCode: String): UIRemoteCtrlSessionState()
|
||||||
@Serializable @SerialName("connected") data class Connected(val remoteCtrl: RemoteCtrlInfo, val sessionCode: String): UIRemoteCtrlSessionState()
|
@Serializable @SerialName("connected") data class Connected(val remoteCtrl: RemoteCtrlInfo, val sessionCode: String): UIRemoteCtrlSessionState()
|
||||||
|
@ -170,6 +170,7 @@ class AppPreferences {
|
|||||||
|
|
||||||
val confirmRemoteSessions = mkBoolPreference(SHARED_PREFS_CONFIRM_REMOTE_SESSIONS, false)
|
val confirmRemoteSessions = mkBoolPreference(SHARED_PREFS_CONFIRM_REMOTE_SESSIONS, false)
|
||||||
val connectRemoteViaMulticast = mkBoolPreference(SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST, false)
|
val connectRemoteViaMulticast = mkBoolPreference(SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST, false)
|
||||||
|
val connectRemoteViaMulticastAuto = mkBoolPreference(SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST_AUTO, true)
|
||||||
val offerRemoteMulticast = mkBoolPreference(SHARED_PREFS_OFFER_REMOTE_MULTICAST, true)
|
val offerRemoteMulticast = mkBoolPreference(SHARED_PREFS_OFFER_REMOTE_MULTICAST, true)
|
||||||
|
|
||||||
private fun mkIntPreference(prefName: String, default: Int) =
|
private fun mkIntPreference(prefName: String, default: Int) =
|
||||||
@ -314,6 +315,7 @@ class AppPreferences {
|
|||||||
private const val SHARED_PREFS_DEVICE_NAME_FOR_REMOTE_ACCESS = "DeviceNameForRemoteAccess"
|
private const val SHARED_PREFS_DEVICE_NAME_FOR_REMOTE_ACCESS = "DeviceNameForRemoteAccess"
|
||||||
private const val SHARED_PREFS_CONFIRM_REMOTE_SESSIONS = "ConfirmRemoteSessions"
|
private const val SHARED_PREFS_CONFIRM_REMOTE_SESSIONS = "ConfirmRemoteSessions"
|
||||||
private const val SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST = "ConnectRemoteViaMulticast"
|
private const val SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST = "ConnectRemoteViaMulticast"
|
||||||
|
private const val SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "ConnectRemoteViaMulticastAuto"
|
||||||
private const val SHARED_PREFS_OFFER_REMOTE_MULTICAST = "OfferRemoteMulticast"
|
private const val SHARED_PREFS_OFFER_REMOTE_MULTICAST = "OfferRemoteMulticast"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1432,14 +1434,25 @@ object ChatController {
|
|||||||
|
|
||||||
suspend fun connectRemoteCtrl(desktopAddress: String): Pair<SomeRemoteCtrl?, CR.ChatCmdError?> {
|
suspend fun connectRemoteCtrl(desktopAddress: String): Pair<SomeRemoteCtrl?, CR.ChatCmdError?> {
|
||||||
val r = sendCmd(null, CC.ConnectRemoteCtrl(desktopAddress))
|
val r = sendCmd(null, CC.ConnectRemoteCtrl(desktopAddress))
|
||||||
if (r is CR.RemoteCtrlConnecting) return SomeRemoteCtrl(r.remoteCtrl_, r.ctrlAppInfo, r.appVersion) to null
|
return if (r is CR.RemoteCtrlConnecting) SomeRemoteCtrl(r.remoteCtrl_, r.ctrlAppInfo, r.appVersion) to null
|
||||||
else if (r is CR.ChatCmdError) return null to r
|
else if (r is CR.ChatCmdError) null to r
|
||||||
else throw Exception("connectRemoteCtrl error: ${r.responseType} ${r.details}")
|
else {
|
||||||
|
apiErrorAlert("connectRemoteCtrl", generalGetString(MR.strings.error_alert_title), r)
|
||||||
|
null to null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun findKnownRemoteCtrl(): Boolean = sendCommandOkResp(null, CC.FindKnownRemoteCtrl())
|
suspend fun findKnownRemoteCtrl(): Boolean = sendCommandOkResp(null, CC.FindKnownRemoteCtrl())
|
||||||
|
|
||||||
suspend fun confirmRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(null, CC.ConfirmRemoteCtrl(rcId))
|
suspend fun confirmRemoteCtrl(rcId: Long): Pair<SomeRemoteCtrl?, CR.ChatCmdError?> {
|
||||||
|
val r = sendCmd(null, CC.ConfirmRemoteCtrl(remoteCtrlId = rcId))
|
||||||
|
return if (r is CR.RemoteCtrlConnecting) SomeRemoteCtrl(r.remoteCtrl_, r.ctrlAppInfo, r.appVersion) to null
|
||||||
|
else if (r is CR.ChatCmdError) null to r
|
||||||
|
else {
|
||||||
|
apiErrorAlert("confirmRemoteCtrl", generalGetString(MR.strings.error_alert_title), r)
|
||||||
|
null to null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun verifyRemoteCtrlSession(sessionCode: String): RemoteCtrlInfo? {
|
suspend fun verifyRemoteCtrlSession(sessionCode: String): RemoteCtrlInfo? {
|
||||||
val r = sendCmd(null, CC.VerifyRemoteCtrlSession(sessionCode))
|
val r = sendCmd(null, CC.VerifyRemoteCtrlSession(sessionCode))
|
||||||
@ -1857,8 +1870,15 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CR.RemoteCtrlFound -> {
|
is CR.RemoteCtrlFound -> {
|
||||||
// TODO multicast
|
val sess = chatModel.remoteCtrlSession.value
|
||||||
Log.d(TAG, "RemoteCtrlFound: ${r.remoteCtrl}")
|
if (sess != null && sess.sessionState is UIRemoteCtrlSessionState.Searching) {
|
||||||
|
val state = UIRemoteCtrlSessionState.Found(remoteCtrl = r.remoteCtrl, compatible = r.compatible)
|
||||||
|
chatModel.remoteCtrlSession.value = RemoteCtrlSession(
|
||||||
|
ctrlAppInfo = r.ctrlAppInfo_,
|
||||||
|
appVersion = r.appVersion,
|
||||||
|
sessionState = state
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is CR.RemoteCtrlSessionCode -> {
|
is CR.RemoteCtrlSessionCode -> {
|
||||||
val state = UIRemoteCtrlSessionState.PendingConfirmation(remoteCtrl_ = r.remoteCtrl_, sessionCode = r.sessionCode)
|
val state = UIRemoteCtrlSessionState.PendingConfirmation(remoteCtrl_ = r.remoteCtrl_, sessionCode = r.sessionCode)
|
||||||
@ -1870,7 +1890,13 @@ object ChatController {
|
|||||||
chatModel.remoteCtrlSession.value = chatModel.remoteCtrlSession.value?.copy(sessionState = state)
|
chatModel.remoteCtrlSession.value = chatModel.remoteCtrlSession.value?.copy(sessionState = state)
|
||||||
}
|
}
|
||||||
is CR.RemoteCtrlStopped -> {
|
is CR.RemoteCtrlStopped -> {
|
||||||
switchToLocalSession()
|
val sess = chatModel.remoteCtrlSession.value
|
||||||
|
if (sess != null) {
|
||||||
|
chatModel.remoteCtrlSession.value = null
|
||||||
|
if (sess.sessionState is UIRemoteCtrlSessionState.Connected) {
|
||||||
|
switchToLocalSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else ->
|
else ->
|
||||||
Log.d(TAG , "unsupported event: ${r.responseType}")
|
Log.d(TAG , "unsupported event: ${r.responseType}")
|
||||||
@ -3782,7 +3808,7 @@ sealed class CR {
|
|||||||
@Serializable @SerialName("remoteFileStored") class RemoteFileStored(val remoteHostId: Long, val remoteFileSource: CryptoFile): CR()
|
@Serializable @SerialName("remoteFileStored") class RemoteFileStored(val remoteHostId: Long, val remoteFileSource: CryptoFile): CR()
|
||||||
// remote events (mobile)
|
// remote events (mobile)
|
||||||
@Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List<RemoteCtrlInfo>): CR()
|
@Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List<RemoteCtrlInfo>): CR()
|
||||||
@Serializable @SerialName("remoteCtrlFound") class RemoteCtrlFound(val remoteCtrl: RemoteCtrlInfo): CR()
|
@Serializable @SerialName("remoteCtrlFound") class RemoteCtrlFound(val remoteCtrl: RemoteCtrlInfo, val ctrlAppInfo_: CtrlAppInfo?, val appVersion: String, val compatible: Boolean): CR()
|
||||||
@Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrl_: RemoteCtrlInfo?, val ctrlAppInfo: CtrlAppInfo, val appVersion: String): CR()
|
@Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrl_: RemoteCtrlInfo?, val ctrlAppInfo: CtrlAppInfo, val appVersion: String): CR()
|
||||||
@Serializable @SerialName("remoteCtrlSessionCode") class RemoteCtrlSessionCode(val remoteCtrl_: RemoteCtrlInfo?, val sessionCode: String): CR()
|
@Serializable @SerialName("remoteCtrlSessionCode") class RemoteCtrlSessionCode(val remoteCtrl_: RemoteCtrlInfo?, val sessionCode: String): CR()
|
||||||
@Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrl: RemoteCtrlInfo): CR()
|
@Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrl: RemoteCtrlInfo): CR()
|
||||||
@ -4080,7 +4106,11 @@ sealed class CR {
|
|||||||
is RemoteHostStopped -> "remote host ID: $remoteHostId_"
|
is RemoteHostStopped -> "remote host ID: $remoteHostId_"
|
||||||
is RemoteFileStored -> "remote host ID: $remoteHostId\nremoteFileSource:\n" + json.encodeToString(remoteFileSource)
|
is RemoteFileStored -> "remote host ID: $remoteHostId\nremoteFileSource:\n" + json.encodeToString(remoteFileSource)
|
||||||
is RemoteCtrlList -> json.encodeToString(remoteCtrls)
|
is RemoteCtrlList -> json.encodeToString(remoteCtrls)
|
||||||
is RemoteCtrlFound -> json.encodeToString(remoteCtrl)
|
is RemoteCtrlFound -> "remote ctrl: " + json.encodeToString(remoteCtrl) +
|
||||||
|
"\nctrlAppInfo: " +
|
||||||
|
(if (ctrlAppInfo_ == null) "null" else json.encodeToString(ctrlAppInfo_)) +
|
||||||
|
"\nappVersion: $appVersion" +
|
||||||
|
"\ncompatible: $compatible"
|
||||||
is RemoteCtrlConnecting ->
|
is RemoteCtrlConnecting ->
|
||||||
"remote ctrl: " +
|
"remote ctrl: " +
|
||||||
(if (remoteCtrl_ == null) "null" else json.encodeToString(remoteCtrl_)) +
|
(if (remoteCtrl_ == null) "null" else json.encodeToString(remoteCtrl_)) +
|
||||||
|
@ -125,7 +125,7 @@ fun DefaultConfigurableTextField(
|
|||||||
keyboardType: KeyboardType = KeyboardType.Text,
|
keyboardType: KeyboardType = KeyboardType.Text,
|
||||||
dependsOn: State<Any?>? = null,
|
dependsOn: State<Any?>? = null,
|
||||||
) {
|
) {
|
||||||
var valid by remember { mutableStateOf(validKey(state.value.text)) }
|
var valid by remember { mutableStateOf(isValid(state.value.text)) }
|
||||||
var showKey by remember { mutableStateOf(false) }
|
var showKey by remember { mutableStateOf(false) }
|
||||||
val icon = if (valid) {
|
val icon = if (valid) {
|
||||||
if (showKey) painterResource(MR.images.ic_visibility_off_filled) else painterResource(MR.images.ic_visibility_filled)
|
if (showKey) painterResource(MR.images.ic_visibility_off_filled) else painterResource(MR.images.ic_visibility_filled)
|
||||||
|
@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalClipboardManager
|
|||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@ -37,6 +38,7 @@ import chat.simplex.common.views.newchat.QRCodeScanner
|
|||||||
import chat.simplex.common.views.usersettings.PreferenceToggle
|
import chat.simplex.common.views.usersettings.PreferenceToggle
|
||||||
import chat.simplex.common.views.usersettings.SettingsActionItem
|
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
@ -69,15 +71,21 @@ fun ConnectDesktopView(close: () -> Unit) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) {
|
private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) {
|
||||||
|
val showConnectScreen = remember { mutableStateOf(true) }
|
||||||
val sessionAddress = remember { mutableStateOf("") }
|
val sessionAddress = remember { mutableStateOf("") }
|
||||||
val remoteCtrls = remember { mutableStateListOf<RemoteCtrlInfo>() }
|
val remoteCtrls = remember { mutableStateListOf<RemoteCtrlInfo>() }
|
||||||
val session = remember { chatModel.remoteCtrlSession }.value
|
val session = remember { chatModel.remoteCtrlSession }.value
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
if (session != null) {
|
val discovery = if (session == null) null else session.sessionState is UIRemoteCtrlSessionState.Searching
|
||||||
|
if (discovery == true || (discovery == null && !showConnectScreen.value)) {
|
||||||
|
SearchingDesktop(deviceName, remoteCtrls)
|
||||||
|
} else if (session != null) {
|
||||||
when (session.sessionState) {
|
when (session.sessionState) {
|
||||||
is UIRemoteCtrlSessionState.Starting -> ConnectingDesktop(session, null)
|
is UIRemoteCtrlSessionState.Starting -> ConnectingDesktop(session, null)
|
||||||
|
is UIRemoteCtrlSessionState.Searching -> SearchingDesktop(deviceName, remoteCtrls)
|
||||||
|
is UIRemoteCtrlSessionState.Found -> FoundDesktop(session, session.sessionState.remoteCtrl, session.sessionState.compatible, remember { controller.appPrefs.connectRemoteViaMulticastAuto.state }, deviceName, remoteCtrls, sessionAddress)
|
||||||
is UIRemoteCtrlSessionState.Connecting -> ConnectingDesktop(session, session.sessionState.remoteCtrl_)
|
is UIRemoteCtrlSessionState.Connecting -> ConnectingDesktop(session, session.sessionState.remoteCtrl_)
|
||||||
is UIRemoteCtrlSessionState.PendingConfirmation -> {
|
is UIRemoteCtrlSessionState.PendingConfirmation -> {
|
||||||
if (controller.appPrefs.confirmRemoteSessions.get() || session.sessionState.remoteCtrl_ == null) {
|
if (controller.appPrefs.confirmRemoteSessions.get() || session.sessionState.remoteCtrl_ == null) {
|
||||||
@ -97,11 +105,21 @@ private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) {
|
|||||||
}
|
}
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
}
|
}
|
||||||
DisposableEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
setDeviceName(deviceName)
|
setDeviceName(deviceName)
|
||||||
updateRemoteCtrls(remoteCtrls)
|
updateRemoteCtrls(remoteCtrls)
|
||||||
|
val useMulticast = useMulticast(remoteCtrls)
|
||||||
|
showConnectScreen.value = !useMulticast
|
||||||
|
if (chatModel.remoteCtrlSession.value != null) {
|
||||||
|
disconnectDesktop()
|
||||||
|
} else if (useMulticast) {
|
||||||
|
findKnownDesktop(showConnectScreen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
if (chatModel.remoteCtrlSession.value != null) {
|
if (chatModel.remoteCtrlSession.value != null) {
|
||||||
|
showConnectScreen.value = false
|
||||||
disconnectDesktop()
|
disconnectDesktop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,7 +164,75 @@ private fun ConnectingDesktop(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
|
|||||||
SectionSpacer()
|
SectionSpacer()
|
||||||
|
|
||||||
SectionView {
|
SectionView {
|
||||||
DisconnectButton(::disconnectDesktop)
|
DisconnectButton(onClick = ::disconnectDesktop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SearchingDesktop(deviceName: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
|
||||||
|
AppBarTitle(stringResource(MR.strings.connecting_to_desktop))
|
||||||
|
SectionView(stringResource(MR.strings.this_device_name).uppercase()) {
|
||||||
|
DevicesView(deviceName, remoteCtrls) {
|
||||||
|
if (it != "") {
|
||||||
|
setDeviceName(it)
|
||||||
|
controller.appPrefs.deviceNameForRemoteAccess.set(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SectionDividerSpaced()
|
||||||
|
SectionView(stringResource(MR.strings.found_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
||||||
|
Text(stringResource(MR.strings.waiting_for_desktop), fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
|
SectionSpacer()
|
||||||
|
DisconnectButton(stringResource(MR.strings.scan_QR_code).replace('\n', ' '), MR.images.ic_qr_code, ::disconnectDesktop)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FoundDesktop(
|
||||||
|
session: RemoteCtrlSession,
|
||||||
|
rc: RemoteCtrlInfo,
|
||||||
|
compatible: Boolean,
|
||||||
|
connectRemoteViaMulticastAuto: State<Boolean>,
|
||||||
|
deviceName: String,
|
||||||
|
remoteCtrls: SnapshotStateList<RemoteCtrlInfo>,
|
||||||
|
sessionAddress: MutableState<String>,
|
||||||
|
) {
|
||||||
|
AppBarTitle(stringResource(MR.strings.found_desktop))
|
||||||
|
SectionView(stringResource(MR.strings.this_device_name).uppercase()) {
|
||||||
|
DevicesView(deviceName, remoteCtrls) {
|
||||||
|
if (it != "") {
|
||||||
|
setDeviceName(it)
|
||||||
|
controller.appPrefs.deviceNameForRemoteAccess.set(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SectionDividerSpaced()
|
||||||
|
SectionView(stringResource(MR.strings.found_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
||||||
|
CtrlDeviceNameText(session, rc)
|
||||||
|
CtrlDeviceVersionText(session)
|
||||||
|
if (!compatible) {
|
||||||
|
Text(stringResource(MR.strings.not_compatible), color = MaterialTheme.colors.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
|
if (compatible) {
|
||||||
|
SectionItemView({ confirmKnownDesktop(sessionAddress, rc) }) {
|
||||||
|
Icon(painterResource(MR.images.ic_check), generalGetString(MR.strings.connect_button), tint = MaterialTheme.colors.secondary)
|
||||||
|
TextIconSpaced(false)
|
||||||
|
Text(generalGetString(MR.strings.connect_button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compatible || !connectRemoteViaMulticastAuto.value) {
|
||||||
|
DisconnectButton(stringResource(MR.strings.cancel_verb), onClick = ::disconnectDesktop)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compatible && connectRemoteViaMulticastAuto.value) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
confirmKnownDesktop(sessionAddress, rc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +260,7 @@ private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessC
|
|||||||
}
|
}
|
||||||
|
|
||||||
SectionView {
|
SectionView {
|
||||||
DisconnectButton(::disconnectDesktop)
|
DisconnectButton(onClick = ::disconnectDesktop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +268,7 @@ private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessC
|
|||||||
private fun CtrlDeviceNameText(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
|
private fun CtrlDeviceNameText(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
|
||||||
val newDesktop = annotatedStringResource(MR.strings.new_desktop)
|
val newDesktop = annotatedStringResource(MR.strings.new_desktop)
|
||||||
val text = remember(rc) {
|
val text = remember(rc) {
|
||||||
var t = AnnotatedString(rc?.deviceViewName ?: session.ctrlAppInfo.deviceName)
|
var t = AnnotatedString(rc?.deviceViewName ?: session.ctrlAppInfo?.deviceName ?: "")
|
||||||
if (rc == null) {
|
if (rc == null) {
|
||||||
t = t + AnnotatedString(" ") + newDesktop
|
t = t + AnnotatedString(" ") + newDesktop
|
||||||
}
|
}
|
||||||
@ -195,7 +281,7 @@ private fun CtrlDeviceNameText(session: RemoteCtrlSession, rc: RemoteCtrlInfo?)
|
|||||||
private fun CtrlDeviceVersionText(session: RemoteCtrlSession) {
|
private fun CtrlDeviceVersionText(session: RemoteCtrlSession) {
|
||||||
val thisDeviceVersion = annotatedStringResource(MR.strings.this_device_version, session.appVersion)
|
val thisDeviceVersion = annotatedStringResource(MR.strings.this_device_version, session.appVersion)
|
||||||
val text = remember(session) {
|
val text = remember(session) {
|
||||||
val v = AnnotatedString(session.ctrlAppInfo.appVersionRange.maxVersion)
|
val v = AnnotatedString(session.ctrlAppInfo?.appVersionRange?.maxVersion ?: "")
|
||||||
var t = AnnotatedString("v$v")
|
var t = AnnotatedString("v$v")
|
||||||
if (v.text != session.appVersion) {
|
if (v.text != session.appVersion) {
|
||||||
t = t + AnnotatedString(" ") + thisDeviceVersion
|
t = t + AnnotatedString(" ") + thisDeviceVersion
|
||||||
@ -243,7 +329,8 @@ private fun SessionCodeText(code: String) {
|
|||||||
private fun DevicesView(deviceName: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>, updateDeviceName: (String) -> Unit) {
|
private fun DevicesView(deviceName: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>, updateDeviceName: (String) -> Unit) {
|
||||||
DeviceNameField(deviceName) { updateDeviceName(it) }
|
DeviceNameField(deviceName) { updateDeviceName(it) }
|
||||||
if (remoteCtrls.isNotEmpty()) {
|
if (remoteCtrls.isNotEmpty()) {
|
||||||
SectionItemView({ ModalManager.start.showModal { LinkedDesktopsView(remoteCtrls) } }) {
|
SectionItemView({ ModalManager.start.showModal { LinkedDesktopsView(remoteCtrls) }
|
||||||
|
}) {
|
||||||
Text(generalGetString(MR.strings.linked_desktops))
|
Text(generalGetString(MR.strings.linked_desktops))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,8 +423,13 @@ private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
|
|||||||
PreferenceToggle(stringResource(MR.strings.verify_connections), remember { controller.appPrefs.confirmRemoteSessions.state }.value) {
|
PreferenceToggle(stringResource(MR.strings.verify_connections), remember { controller.appPrefs.confirmRemoteSessions.state }.value) {
|
||||||
controller.appPrefs.confirmRemoteSessions.set(it)
|
controller.appPrefs.confirmRemoteSessions.set(it)
|
||||||
}
|
}
|
||||||
PreferenceToggle(stringResource(MR.strings.discover_on_network), remember { controller.appPrefs.connectRemoteViaMulticast.state }.value && false) {
|
PreferenceToggle(stringResource(MR.strings.discover_on_network), remember { controller.appPrefs.connectRemoteViaMulticast.state }.value) {
|
||||||
controller.appPrefs.confirmRemoteSessions.set(it)
|
controller.appPrefs.connectRemoteViaMulticast.set(it)
|
||||||
|
}
|
||||||
|
if (remember { controller.appPrefs.connectRemoteViaMulticast.state }.value) {
|
||||||
|
PreferenceToggle(stringResource(MR.strings.multicast_connect_automatically), remember { controller.appPrefs.connectRemoteViaMulticastAuto.state }.value) {
|
||||||
|
controller.appPrefs.connectRemoteViaMulticastAuto.set(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
@ -355,13 +447,11 @@ private fun setDeviceName(name: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRemoteCtrls(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
|
private suspend fun updateRemoteCtrls(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
|
||||||
withBGApi {
|
val res = controller.listRemoteCtrls()
|
||||||
val res = controller.listRemoteCtrls()
|
if (res != null) {
|
||||||
if (res != null) {
|
remoteCtrls.clear()
|
||||||
remoteCtrls.clear()
|
remoteCtrls.addAll(res)
|
||||||
remoteCtrls.addAll(res)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,9 +459,34 @@ private fun processDesktopQRCode(sessionAddress: MutableState<String>, resp: Str
|
|||||||
connectDesktopAddress(sessionAddress, resp)
|
connectDesktopAddress(sessionAddress, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connectDesktopAddress(sessionAddress: MutableState<String>, addr: String) {
|
private fun findKnownDesktop(showConnectScreen: MutableState<Boolean>) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val res = controller.connectRemoteCtrl(desktopAddress = addr)
|
if (controller.findKnownRemoteCtrl()) {
|
||||||
|
chatModel.remoteCtrlSession.value = RemoteCtrlSession(
|
||||||
|
ctrlAppInfo = null,
|
||||||
|
appVersion = "",
|
||||||
|
sessionState = UIRemoteCtrlSessionState.Searching
|
||||||
|
)
|
||||||
|
showConnectScreen.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun confirmKnownDesktop(sessionAddress: MutableState<String>, rc: RemoteCtrlInfo) {
|
||||||
|
connectDesktop(sessionAddress) {
|
||||||
|
controller.confirmRemoteCtrl(rc.remoteCtrlId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectDesktopAddress(sessionAddress: MutableState<String>, addr: String) {
|
||||||
|
connectDesktop(sessionAddress) {
|
||||||
|
controller.connectRemoteCtrl(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectDesktop(sessionAddress: MutableState<String>, connect: suspend () -> Pair<SomeRemoteCtrl?, CR.ChatCmdError?>) {
|
||||||
|
withBGApi {
|
||||||
|
val res = connect()
|
||||||
if (res.first != null) {
|
if (res.first != null) {
|
||||||
val (rc_, ctrlAppInfo, v) = res.first!!
|
val (rc_, ctrlAppInfo, v) = res.first!!
|
||||||
sessionAddress.value = ""
|
sessionAddress.value = ""
|
||||||
@ -409,18 +524,25 @@ private fun verifyDesktopSessionCode(remoteCtrls: SnapshotStateList<RemoteCtrlIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DisconnectButton(onClick: () -> Unit) {
|
private fun DisconnectButton(label: String = generalGetString(MR.strings.disconnect_remote_host), icon: ImageResource = MR.images.ic_close, onClick: () -> Unit) {
|
||||||
SectionItemView(onClick) {
|
SectionItemView(onClick) {
|
||||||
Icon(painterResource(MR.images.ic_close), generalGetString(MR.strings.disconnect_remote_host), tint = MaterialTheme.colors.secondary)
|
Icon(painterResource(icon), label, tint = MaterialTheme.colors.secondary)
|
||||||
TextIconSpaced(false)
|
TextIconSpaced(false)
|
||||||
Text(generalGetString(MR.strings.disconnect_remote_host))
|
Text(label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun useMulticast(remoteCtrls: List<RemoteCtrlInfo>): Boolean =
|
||||||
|
controller.appPrefs.connectRemoteViaMulticast.get() && remoteCtrls.isNotEmpty()
|
||||||
|
|
||||||
private fun disconnectDesktop(close: (() -> Unit)? = null) {
|
private fun disconnectDesktop(close: (() -> Unit)? = null) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
controller.stopRemoteCtrl()
|
controller.stopRemoteCtrl()
|
||||||
switchToLocalSession()
|
if (chatModel.remoteCtrlSession.value?.sessionState is UIRemoteCtrlSessionState.Connected) {
|
||||||
|
switchToLocalSession()
|
||||||
|
} else {
|
||||||
|
chatModel.remoteCtrlSession.value = null
|
||||||
|
}
|
||||||
close?.invoke()
|
close?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import chat.simplex.common.views.chat.item.ItemAction
|
|||||||
import chat.simplex.common.views.chatlist.*
|
import chat.simplex.common.views.chatlist.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.newchat.QRCode
|
import chat.simplex.common.views.newchat.QRCode
|
||||||
|
import chat.simplex.common.views.usersettings.PreferenceToggle
|
||||||
import chat.simplex.common.views.usersettings.SettingsActionItemWithContent
|
import chat.simplex.common.views.usersettings.SettingsActionItemWithContent
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
@ -90,6 +91,9 @@ fun ConnectMobileLayout(
|
|||||||
SectionView(generalGetString(MR.strings.this_device_name).uppercase()) {
|
SectionView(generalGetString(MR.strings.this_device_name).uppercase()) {
|
||||||
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
|
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
|
||||||
SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile))
|
SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile))
|
||||||
|
PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), remember { controller.appPrefs.offerRemoteMulticast.state }.value) {
|
||||||
|
controller.appPrefs.offerRemoteMulticast.set(it)
|
||||||
|
}
|
||||||
SectionDividerSpaced(maxBottomPadding = false)
|
SectionDividerSpaced(maxBottomPadding = false)
|
||||||
}
|
}
|
||||||
SectionView(stringResource(MR.strings.devices).uppercase()) {
|
SectionView(stringResource(MR.strings.devices).uppercase()) {
|
||||||
@ -266,7 +270,7 @@ private fun showAddingMobileDevice(connecting: MutableState<Boolean>) {
|
|||||||
}
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.startRemoteHost(null)
|
val r = chatModel.controller.startRemoteHost(null, controller.appPrefs.offerRemoteMulticast.get())
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
connecting.value = true
|
connecting.value = true
|
||||||
invitation.value = r.second
|
invitation.value = r.second
|
||||||
@ -309,7 +313,7 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
|
|||||||
)
|
)
|
||||||
var remoteHostId by rememberSaveable { mutableStateOf<Long?>(null) }
|
var remoteHostId by rememberSaveable { mutableStateOf<Long?>(null) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val r = chatModel.controller.startRemoteHost(rh.remoteHostId)
|
val r = chatModel.controller.startRemoteHost(rh.remoteHostId, controller.appPrefs.offerRemoteMulticast.get())
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
val (rh_, inv) = r
|
val (rh_, inv) = r
|
||||||
connecting.value = true
|
connecting.value = true
|
||||||
|
@ -1670,6 +1670,8 @@
|
|||||||
<string name="desktop_connection_terminated">Connection terminated</string>
|
<string name="desktop_connection_terminated">Connection terminated</string>
|
||||||
<string name="session_code">Session code</string>
|
<string name="session_code">Session code</string>
|
||||||
<string name="connecting_to_desktop">Connecting to desktop</string>
|
<string name="connecting_to_desktop">Connecting to desktop</string>
|
||||||
|
<string name="waiting_for_desktop">Waiting for desktop…</string>
|
||||||
|
<string name="found_desktop">Found desktop</string>
|
||||||
<string name="connect_to_desktop">Connect to desktop</string>
|
<string name="connect_to_desktop">Connect to desktop</string>
|
||||||
<string name="connected_to_desktop">Connected to desktop</string>
|
<string name="connected_to_desktop">Connected to desktop</string>
|
||||||
<string name="connected_desktop">Connected desktop</string>
|
<string name="connected_desktop">Connected desktop</string>
|
||||||
@ -1681,9 +1683,12 @@
|
|||||||
<string name="scan_qr_code_from_desktop">Scan QR code from desktop</string>
|
<string name="scan_qr_code_from_desktop">Scan QR code from desktop</string>
|
||||||
<string name="desktop_address">Desktop address</string>
|
<string name="desktop_address">Desktop address</string>
|
||||||
<string name="verify_connections">Verify connections</string>
|
<string name="verify_connections">Verify connections</string>
|
||||||
<string name="discover_on_network">Discover on network</string>
|
<string name="discover_on_network">Discover via local network</string>
|
||||||
|
<string name="multicast_discoverable_via_local_network">Discoverable via local network</string>
|
||||||
|
<string name="multicast_connect_automatically">Connect automatically</string>
|
||||||
<string name="paste_desktop_address">Paste desktop address</string>
|
<string name="paste_desktop_address">Paste desktop address</string>
|
||||||
<string name="desktop_device">Desktop</string>
|
<string name="desktop_device">Desktop</string>
|
||||||
|
<string name="not_compatible">Not compatible!</string>
|
||||||
|
|
||||||
<!-- Under development -->
|
<!-- Under development -->
|
||||||
<string name="in_developing_title">Coming soon!</string>
|
<string name="in_developing_title">Coming soon!</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user