Skip to content

Commit 7dcc67a

Browse files
committed
Display multiple IP addresses when available
1 parent 9f054e5 commit 7dcc67a

File tree

7 files changed

+63
-105
lines changed

7 files changed

+63
-105
lines changed

app/src/main/java/com/ismartcoding/plain/ui/components/WebAddress.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ import com.ismartcoding.plain.preference.HttpsPreference
1919
import com.ismartcoding.plain.ui.base.PageIndicator
2020
import com.ismartcoding.plain.ui.base.Tips
2121
import com.ismartcoding.plain.ui.base.VerticalSpace
22+
import com.ismartcoding.plain.ui.models.MainViewModel
2223
import kotlinx.coroutines.launch
2324

2425
// https://developer.android.com/jetpack/compose/layouts/pager
2526
@OptIn(ExperimentalFoundationApi::class)
2627
@Composable
2728
fun WebAddress(
2829
context: Context,
30+
mainVM: MainViewModel
2931
) {
3032
val initialPage = if (TempData.webHttps) 1 else 0
3133
val pagerState = rememberPagerState(initialPage = initialPage, pageCount = {
@@ -51,7 +53,7 @@ fun WebAddress(
5153
) { page ->
5254
Column {
5355
val isHttps = page != 0
54-
WebAddressBar(context, isHttps)
56+
WebAddressBar(context, mainVM, isHttps)
5557
Tips(text = stringResource(id = R.string.enter_this_address_tips), modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp))
5658
VerticalSpace(dp = 8.dp)
5759
}

app/src/main/java/com/ismartcoding/plain/ui/components/WebAddressBar.kt

Lines changed: 47 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ import android.content.Context
55
import androidx.compose.foundation.ExperimentalFoundationApi
66
import androidx.compose.foundation.Image
77
import androidx.compose.foundation.background
8-
import androidx.compose.foundation.layout.Arrangement
9-
import androidx.compose.foundation.layout.Box
108
import androidx.compose.foundation.layout.Column
119
import androidx.compose.foundation.layout.Row
1210
import androidx.compose.foundation.layout.Spacer
1311
import androidx.compose.foundation.layout.fillMaxWidth
1412
import androidx.compose.foundation.layout.height
1513
import androidx.compose.foundation.layout.padding
1614
import androidx.compose.foundation.layout.size
17-
import androidx.compose.foundation.layout.wrapContentSize
1815
import androidx.compose.foundation.shape.RoundedCornerShape
1916
import androidx.compose.foundation.text.ClickableText
2017
import androidx.compose.foundation.text.selection.SelectionContainer
@@ -23,39 +20,34 @@ import androidx.compose.material3.Button
2320
import androidx.compose.material3.MaterialTheme
2421
import androidx.compose.material3.Text
2522
import androidx.compose.runtime.Composable
26-
import androidx.compose.runtime.LaunchedEffect
2723
import androidx.compose.runtime.getValue
2824
import androidx.compose.runtime.mutableStateOf
2925
import androidx.compose.runtime.remember
3026
import androidx.compose.runtime.rememberCoroutineScope
3127
import androidx.compose.runtime.setValue
3228
import androidx.compose.ui.Alignment
3329
import androidx.compose.ui.Modifier
34-
import androidx.compose.ui.draw.clip
3530
import androidx.compose.ui.graphics.asImageBitmap
3631
import androidx.compose.ui.res.stringResource
3732
import androidx.compose.ui.text.AnnotatedString
3833
import androidx.compose.ui.text.TextStyle
3934
import androidx.compose.ui.unit.dp
4035
import androidx.compose.ui.unit.sp
4136
import com.ismartcoding.lib.channel.Channel
42-
import com.ismartcoding.lib.helpers.NetworkHelper
4337
import com.ismartcoding.plain.R
4438
import com.ismartcoding.plain.TempData
4539
import com.ismartcoding.plain.clipboardManager
46-
import com.ismartcoding.plain.features.WindowFocusChangedEvent
4740
import com.ismartcoding.plain.features.locale.LocaleHelper
4841
import com.ismartcoding.plain.helpers.AppHelper
4942
import com.ismartcoding.plain.helpers.QrCodeGenerateHelper
5043
import com.ismartcoding.plain.preference.HttpPortPreference
5144
import com.ismartcoding.plain.preference.HttpsPortPreference
5245
import com.ismartcoding.plain.ui.base.HorizontalSpace
53-
import com.ismartcoding.plain.ui.base.PDropdownMenu
54-
import com.ismartcoding.plain.ui.base.PDropdownMenuItem
5546
import com.ismartcoding.plain.ui.base.PIconButton
5647
import com.ismartcoding.plain.ui.base.RadioDialog
5748
import com.ismartcoding.plain.ui.base.RadioDialogOption
5849
import com.ismartcoding.plain.ui.helpers.DialogHelper
50+
import com.ismartcoding.plain.ui.models.MainViewModel
5951
import com.ismartcoding.plain.web.HttpServerManager
6052
import kotlinx.coroutines.Dispatchers
6153
import kotlinx.coroutines.launch
@@ -64,112 +56,73 @@ import kotlinx.coroutines.launch
6456
@Composable
6557
fun WebAddressBar(
6658
context: Context,
59+
mainVM: MainViewModel,
6760
isHttps: Boolean,
6861
) {
6962
val port = if (isHttps) TempData.httpsPort else TempData.httpPort
7063
var portDialogVisible by remember { mutableStateOf(false) }
7164
var qrCodeDialogVisible by remember { mutableStateOf(false) }
72-
var ip4 = remember { NetworkHelper.getDeviceIP4().ifEmpty { "127.0.0.1" } }
73-
var ip4s = remember { NetworkHelper.getDeviceIP4s().filter { it != ip4 } }
74-
val showContextMenu = remember { mutableStateOf(false) }
75-
val defaultUrl = remember { mutableStateOf("${if (isHttps) "https" else "http"}://$ip4:${port}") }
65+
var ip4 = mainVM.ip4
66+
var ip4s = mainVM.ip4s.ifEmpty { listOf("127.0.0.1") }
7667
val scope = rememberCoroutineScope()
77-
val sharedFlow = Channel.sharedFlow
68+
var qrCodeUrl by remember { mutableStateOf("") }
7869

79-
LaunchedEffect(sharedFlow) {
80-
sharedFlow.collect { event ->
81-
when (event) {
82-
is WindowFocusChangedEvent -> {
83-
if (event.hasFocus) {
84-
ip4 = NetworkHelper.getDeviceIP4().ifEmpty { "127.0.0.1" }
85-
ip4s = NetworkHelper.getDeviceIP4s().filter { it != ip4 }
86-
defaultUrl.value = "${if (isHttps) "https" else "http"}://$ip4:${port}"
87-
}
88-
}
89-
}
90-
}
91-
}
92-
93-
Row(
70+
Column(
9471
Modifier
9572
.fillMaxWidth()
96-
.height(48.dp)
9773
.background(
9874
color = MaterialTheme.colorScheme.surfaceContainerHighest,
9975
shape = RoundedCornerShape(12.dp),
100-
),
101-
verticalAlignment = Alignment.CenterVertically,
102-
) {
103-
SelectionContainer {
104-
ClickableText(
105-
text = AnnotatedString(defaultUrl.value),
106-
modifier = Modifier.padding(start = 16.dp),
107-
style =
108-
TextStyle(
109-
color = MaterialTheme.colorScheme.onSurface,
110-
fontSize = 18.sp,
111-
),
112-
onClick = {
113-
val clip = ClipData.newPlainText(LocaleHelper.getString(R.string.link), defaultUrl.value)
114-
clipboardManager.setPrimaryClip(clip)
115-
DialogHelper.showTextCopiedMessage(defaultUrl.value)
116-
},
11776
)
118-
}
119-
Spacer(modifier = Modifier.weight(1f))
120-
PIconButton(
121-
icon = R.drawable.pen,
122-
modifier = Modifier.size(32.dp),
123-
iconSize = 16.dp,
124-
contentDescription = stringResource(id = R.string.edit),
125-
tint = MaterialTheme.colorScheme.onSurface,
126-
click = {
127-
portDialogVisible = true
128-
},
129-
)
130-
PIconButton(
131-
icon = R.drawable.qr_code,
132-
modifier = Modifier.size(32.dp),
133-
iconSize = 16.dp,
134-
contentDescription = stringResource(id = R.string.qrcode),
135-
tint = MaterialTheme.colorScheme.onSurface,
136-
click = {
137-
qrCodeDialogVisible = true
138-
},
139-
)
140-
if (ip4s.isNotEmpty()) {
141-
Box(
142-
modifier =
143-
Modifier
144-
.wrapContentSize(Alignment.TopEnd),
77+
.padding(vertical = 8.dp)
78+
) {
79+
for (ip in ip4s) {
80+
Row(
81+
Modifier.height(40.dp),
82+
verticalAlignment = Alignment.CenterVertically,
14583
) {
84+
val url = "${if (isHttps) "https" else "http"}://$ip:${port}"
85+
SelectionContainer {
86+
ClickableText(
87+
text = AnnotatedString(url),
88+
modifier = Modifier.padding(start = 16.dp),
89+
style =
90+
TextStyle(
91+
color = MaterialTheme.colorScheme.onSurface,
92+
fontSize = 18.sp,
93+
),
94+
onClick = {
95+
val clip = ClipData.newPlainText(LocaleHelper.getString(R.string.link), url)
96+
clipboardManager.setPrimaryClip(clip)
97+
DialogHelper.showTextCopiedMessage(url)
98+
},
99+
)
100+
}
101+
Spacer(modifier = Modifier.weight(1f))
146102
PIconButton(
147-
icon = R.drawable.ellipsis_vertical,
103+
icon = R.drawable.pen,
148104
modifier = Modifier.size(32.dp),
149105
iconSize = 16.dp,
150-
contentDescription = stringResource(id = R.string.more),
151-
tint = MaterialTheme.colorScheme.onSurfaceVariant,
106+
contentDescription = stringResource(id = R.string.edit),
107+
tint = MaterialTheme.colorScheme.onSurface,
152108
click = {
153-
showContextMenu.value = true
109+
portDialogVisible = true
154110
},
155111
)
156-
PDropdownMenu(
157-
expanded = showContextMenu.value,
158-
onDismissRequest = { showContextMenu.value = false },
159-
) {
160-
ip4s.forEach { ip ->
161-
val url = "${if (isHttps) "https" else "http"}://$ip:${port}"
162-
PDropdownMenuItem(text = { Text(url) }, onClick = {
163-
showContextMenu.value = false
164-
val clip = ClipData.newPlainText(LocaleHelper.getString(R.string.link), url)
165-
clipboardManager.setPrimaryClip(clip)
166-
DialogHelper.showTextCopiedMessage(url)
167-
})
168-
}
169-
}
112+
PIconButton(
113+
icon = R.drawable.qr_code,
114+
modifier = Modifier.size(32.dp),
115+
iconSize = 16.dp,
116+
contentDescription = stringResource(id = R.string.qrcode),
117+
tint = MaterialTheme.colorScheme.onSurface,
118+
click = {
119+
qrCodeUrl = url
120+
qrCodeDialogVisible = true
121+
},
122+
)
123+
HorizontalSpace(dp = 4.dp)
170124
}
171125
}
172-
HorizontalSpace(dp = 4.dp)
173126
}
174127

175128
if (portDialogVisible) {
@@ -203,7 +156,6 @@ fun WebAddressBar(
203156
portDialogVisible = false
204157
}
205158
}
206-
207159
if (qrCodeDialogVisible) {
208160
AlertDialog(
209161
containerColor = MaterialTheme.colorScheme.surface,
@@ -230,7 +182,7 @@ fun WebAddressBar(
230182
color = MaterialTheme.colorScheme.onSurface,
231183
)
232184
Image(
233-
bitmap = QrCodeGenerateHelper.generate(defaultUrl.value, 300, 300).asImageBitmap(),
185+
bitmap = QrCodeGenerateHelper.generate(qrCodeUrl, 300, 300).asImageBitmap(),
234186
contentDescription = stringResource(id = R.string.qrcode),
235187
modifier = Modifier
236188
.size(300.dp)

app/src/main/java/com/ismartcoding/plain/ui/models/MainViewModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class MainViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
3232
}
3333
var isNetworkConnected by savedStateHandle.saveable { mutableStateOf(true) }
3434
var isVPNConnected by savedStateHandle.saveable { mutableStateOf(false) }
35+
var ip4s by savedStateHandle.saveable { mutableStateOf(emptyList<String>()) }
36+
var ip4 by savedStateHandle.saveable { mutableStateOf("") }
3537

3638
fun enableHttpServer(
3739
context: Context,

app/src/main/java/com/ismartcoding/plain/ui/page/root/components/HomeWeb.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fun HomeWeb(
4545
mainVM.enableHttpServer(context, it)
4646
}
4747
if (webEnabled) {
48-
WebAddress(context)
48+
WebAddress(context, mainVM)
4949
}
5050
VerticalSpace(dp = 16.dp)
5151
}

app/src/main/java/com/ismartcoding/plain/ui/page/root/contents/TabContentHome.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ fun TabContentHome(
7272
is WindowFocusChangedEvent -> {
7373
mainVM.isVPNConnected = NetworkHelper.isVPNConnected(context)
7474
mainVM.isNetworkConnected = NetworkHelper.isNetworkConnected(context)
75+
mainVM.ip4s = NetworkHelper.getDeviceIP4s().filter { it.isNotEmpty() }
76+
mainVM.ip4 = NetworkHelper.getDeviceIP4().ifEmpty { "127.0.0.1" }
7577
}
7678
}
7779
}

app/src/main/java/com/ismartcoding/plain/ui/page/web/WebSettingsPage.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ import kotlinx.coroutines.launch
8989
@Composable
9090
fun WebSettingsPage(
9191
navController: NavHostController,
92-
mainViewModel: MainViewModel,
92+
mainVM: MainViewModel,
9393
webVM: WebConsoleViewModel = viewModel(),
9494
) {
9595
WebSettingsProvider {
@@ -191,8 +191,8 @@ fun WebSettingsPage(
191191
item {
192192
TopSpace()
193193
if (webEnabled) {
194-
if (mainViewModel.httpServerError.isNotEmpty()) {
195-
PAlert(title = stringResource(id = R.string.error), description = mainViewModel.httpServerError, AlertType.ERROR) {
194+
if (mainVM.httpServerError.isNotEmpty()) {
195+
PAlert(title = stringResource(id = R.string.error), description = mainVM.httpServerError, AlertType.ERROR) {
196196
if (HttpServerManager.portsInUse.isNotEmpty()) {
197197
PMiniOutlineButton(
198198
label = stringResource(R.string.change_port),
@@ -259,18 +259,18 @@ fun WebSettingsPage(
259259
item {
260260
VerticalSpace(dp = 8.dp)
261261
PMainSwitch(
262-
title = stringResource(id = mainViewModel.httpServerState.getTextId()),
262+
title = stringResource(id = mainVM.httpServerState.getTextId()),
263263
activated = webEnabled,
264-
enable = !mainViewModel.httpServerState.isProcessing()
264+
enable = !mainVM.httpServerState.isProcessing()
265265
) {
266-
mainViewModel.enableHttpServer(context, it)
266+
mainVM.enableHttpServer(context, it)
267267
}
268268
}
269269
if (webEnabled) {
270270
item {
271271
VerticalSpace(dp = 16.dp)
272272
PCard {
273-
WebAddress(context)
273+
WebAddress(context, mainVM)
274274
VerticalSpace(dp = 16.dp)
275275
}
276276
}

lib/src/main/java/com/ismartcoding/lib/helpers/NetworkHelper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ object NetworkHelper {
4545
while (enumIpAddr.hasMoreElements()) {
4646
val inetAddress = enumIpAddr.nextElement()
4747
if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
48-
val ip = inetAddress.getHostAddress() ?: ""
48+
val ip = inetAddress.hostAddress ?: ""
4949
if (ip.isNotEmpty()) {
5050
ips.add(ip)
5151
}

0 commit comments

Comments
 (0)